@sberbusiness/triplex-next-mcp-server 1.44.0-beta → 1.46.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.
Files changed (89) hide show
  1. package/README.md +7 -6
  2. package/bundle/components/AlertContext.md +11 -8
  3. package/bundle/components/AlertProcess.md +13 -10
  4. package/bundle/components/Badge.md +6 -3
  5. package/bundle/components/Button.md +46 -43
  6. package/bundle/components/ButtonDropdownExtended.md +3 -2
  7. package/bundle/components/ButtonIcon.md +7 -4
  8. package/bundle/components/Calendar.md +62 -59
  9. package/bundle/components/Caption.md +26 -23
  10. package/bundle/components/CardAction.md +615 -0
  11. package/bundle/components/Checkbox.md +32 -29
  12. package/bundle/components/CheckboxTreeExtended.md +9 -5
  13. package/bundle/components/Chip.md +51 -47
  14. package/bundle/components/ChipMonthYearPicker.md +344 -0
  15. package/bundle/components/ChipMultiselect.md +1251 -0
  16. package/bundle/components/ChipOptions.md +329 -0
  17. package/bundle/components/ChipSort.md +333 -0
  18. package/bundle/components/ChipSuggest.md +587 -0
  19. package/bundle/components/CodeText.md +26 -23
  20. package/bundle/components/Col.md +1 -2
  21. package/bundle/components/CollapsibleTree.md +8 -5
  22. package/bundle/components/DateField.md +222 -303
  23. package/bundle/components/DatePickerExtended.md +3 -1
  24. package/bundle/components/Dropdown.md +33 -28
  25. package/bundle/components/EmptyView.md +12 -5
  26. package/bundle/components/FooterPage.md +6 -2
  27. package/bundle/components/FormField.md +47 -42
  28. package/bundle/components/FormGroup.md +192 -52
  29. package/bundle/components/Header.md +491 -211
  30. package/bundle/components/HeaderPage.md +6 -2
  31. package/bundle/components/HelpBox.md +7 -1
  32. package/bundle/components/ImageGallery.md +33 -34
  33. package/bundle/components/ImageGalleryExtended.md +33 -34
  34. package/bundle/components/Island.md +16 -13
  35. package/bundle/components/IslandAccordion.md +443 -152
  36. package/bundle/components/IslandWidget.md +622 -157
  37. package/bundle/components/LightBox.md +14 -12
  38. package/bundle/components/Link.md +211 -96
  39. package/bundle/components/ListActionItem.md +7 -4
  40. package/bundle/components/ListItem.md +11 -8
  41. package/bundle/components/ListItemControlsButton.md +16 -13
  42. package/bundle/components/ListItemControlsButtonDropdown.md +9 -6
  43. package/bundle/components/ListItemSelectable.md +7 -4
  44. package/bundle/components/ListMasterFooter.md +8 -5
  45. package/bundle/components/ListMasterHeader.md +8 -5
  46. package/bundle/components/ListSortableItem.md +11 -8
  47. package/bundle/components/ListTableItem.md +7 -4
  48. package/bundle/components/LoaderMiddle.md +7 -4
  49. package/bundle/components/LoaderScreen.md +11 -8
  50. package/bundle/components/LoaderSmall.md +7 -4
  51. package/bundle/components/Marker.md +184 -69
  52. package/bundle/components/MarkerStatus.md +206 -88
  53. package/bundle/components/MaskedField.md +4 -5
  54. package/bundle/components/MasterTable.md +602 -0
  55. package/bundle/components/ModalWindow.md +7 -4
  56. package/bundle/components/MonthYearField.md +3 -1
  57. package/bundle/components/MultiselectField.md +2183 -132
  58. package/bundle/components/Notification.md +12 -9
  59. package/bundle/components/Overlay.md +9 -4
  60. package/bundle/components/Page.md +2 -2
  61. package/bundle/components/Pagination.md +15 -8
  62. package/bundle/components/Row.md +1 -1
  63. package/bundle/components/SelectExtendedField.md +3 -3
  64. package/bundle/components/SelectField.md +466 -148
  65. package/bundle/components/SliderExtended.md +39 -36
  66. package/bundle/components/SmallInput.md +194 -51
  67. package/bundle/components/Suggest.md +535 -99
  68. package/bundle/components/SwipeableArea.md +428 -79
  69. package/bundle/components/TableBasic.md +19 -11
  70. package/bundle/components/TabsExtended.md +24 -21
  71. package/bundle/components/Tag.md +6 -2
  72. package/bundle/components/Text.md +28 -25
  73. package/bundle/components/TextField.md +3 -4
  74. package/bundle/components/ThemeProvider.md +50 -1
  75. package/bundle/components/Title.md +26 -23
  76. package/bundle/components/Tooltip.md +24 -21
  77. package/bundle/components/TopOverlay.md +7 -4
  78. package/bundle/guides/codestyle.md +3 -0
  79. package/bundle/guides/context.md +84 -12
  80. package/bundle/guides/guidelines.md +321 -0
  81. package/bundle/guides/template.md +22 -12
  82. package/bundle/manifest.json +75 -17
  83. package/bundle/release-notes/1.45.0.mdx +117 -0
  84. package/bundle/release-notes/1.46.0.mdx +66 -0
  85. package/bundle/release-notes/1.47.0.mdx +7 -0
  86. package/bundle/tokens.json +6362 -0
  87. package/dist/index.js +177 -28
  88. package/dist/index.js.map +1 -1
  89. package/package.json +1 -1
@@ -3,8 +3,8 @@ component: CollapsibleTree
3
3
  category: CollapsibleTree
4
4
  related: [CollapsibleTreeExtended, CollapsibleTreeNodeHeader, CollapsibleTreeNodeLabel, TreeView, AccordionBase]
5
5
  tokens:
6
- - --triplex-next-CollapsibleTree-Header_Background_Hover
7
- - --triplex-next-CollapsibleTree-Header_Shadow_Focus
6
+ - CollapsibleTree.Header_Background_Hover
7
+ - CollapsibleTree.Header_Shadow_Focus
8
8
  stories: stories/CollapsibleTree/CollapsibleTree.stories.tsx
9
9
  version: "1.0"
10
10
  ---
@@ -102,9 +102,12 @@ Type guard: `isCollapsibleTreeNodeLeaf(node)` — `"content" in node`.
102
102
 
103
103
  ## Дизайн-токены
104
104
 
105
+ Переопределяются через `ThemeProvider` (prop `tokens`) — см. `ThemeProvider-ai.md` →
106
+ «Как переопределять токены». Значения по умолчанию — `src/components/DesignTokens/components/CollapsibleTree.ts`.
107
+
105
108
  ```text
106
- --triplex-next-CollapsibleTree-Header_Background_Hover
107
- --triplex-next-CollapsibleTree-Header_Shadow_Focus
109
+ CollapsibleTree.Header_Background_Hover
110
+ CollapsibleTree.Header_Shadow_Focus
108
111
  ```
109
112
 
110
113
  Цвета disabled-состояния задаются без собственных токенов: шеврон — глобальным
@@ -177,7 +180,7 @@ Type guard: `isCollapsibleTreeNodeLeaf(node)` — `"content" in node`.
177
180
  - Свёрнутая ветка получает `visibility: hidden` на body через
178
181
  `CollapsibleTreeExtended` — Tab не уходит в скрытое содержимое.
179
182
  - Фокус-стиль — `:focus-visible` через токен
180
- `--triplex-next-CollapsibleTree-Header_Shadow_Focus`.
183
+ `CollapsibleTree.Header_Shadow_Focus`.
181
184
 
182
185
  ---
183
186
 
@@ -1,9 +1,7 @@
1
1
  ---
2
2
  component: DateField
3
- category: Form
4
- figma-node: ""
5
- figma-file: ""
6
- related: [DatePickerExtended, Calendar, MaskedField, MonthYearField, DateRange, Tooltip, MobileView]
3
+ category: Date components
4
+ related: [DatePickerExtended, MonthYearField, ChipDatePicker, MaskedField, Calendar, Tooltip]
7
5
  tokens: []
8
6
  stories: stories/DateField/DateField.stories.tsx
9
7
  version: "1.0"
@@ -13,359 +11,280 @@ version: "1.0"
13
11
 
14
12
  ## Назначение
15
13
 
16
- Поле ввода даты с **маской** (`ДД.ММ.ГГГГ`) + выпадающим календарём
17
- (`DatePickerExtended`). Объединяет текстовый ввод с пикером, валидирует
18
- дату (`moment`), синхронизирует состояние input ↔ calendar ↔ внешний
19
- `value`, показывает подсказку-tooltip при невалидном вводе на десктопе.
14
+ Поле ввода и выбора даты. Поле ввода с маской (`MaskedField` + `FormFieldMaskedInput`) и иконкой календаря в постфиксе, по клику раскрывающее выпадающий календарь `DatePickerExtended`. Дату можно как ввести с клавиатуры в формате `дд.мм.гггг`, так и выбрать в календаре.
20
15
 
21
- На мобильных (`MobileView`) ведёт себя как `Dropdown` с масочным input'ом
22
- в заголовке (`DropdownMobileMaskedInput`), без tooltip'а.
16
+ Используй когда: нужен ввод конкретной даты (с днём), и потребителю важна возможность набрать её с клавиатуры, а не только выбрать мышью.
23
17
 
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-тесты ввода/валидации.
18
+ Не используй когда: нужен выбор только месяца и года (`MonthYearField`), диапазона дат (`DateRange`), компактный chip-триггер вместо полноценного поля (`ChipDatePicker`), или календарь без поля ввода (`DatePickerExtended` / `Calendar` напрямую).
56
19
 
57
20
  ---
58
21
 
59
22
  ## Варианты и props
60
23
 
61
- ### `IDateFieldProps`
24
+ `DateField` — обёртка над `DatePickerExtended`, поэтому помимо собственных props принимает все props `DatePickerExtended` (включая props `Calendar`: `limitRange`, `disabledDays`, `markedDays`, `format` и др.), кроме управляемых внутри (`pickedDate`, `onDateChange`, `renderTarget`, `renderDropdownHeaderTarget`), и часть props `MaskedField` (`size`, `status`, `label`). Остальные неперечисленные props пробрасываются в `DatePickerExtended`.
62
25
 
63
- ```ts
64
- extends Omit<IDatePickerExtendedProps,
65
- "pickedDate" | "onDateChange" | "renderTarget" | "renderDropdownHeaderTarget">,
66
- Pick<IMaskedFieldProps, "size" | "status" | "label">
67
- ```
26
+ ### Обязательные props
68
27
 
69
- **Основные:**
28
+ | Prop | Тип | Описание |
29
+ |---|---|---|
30
+ | `value` | `string` | Значение даты в формате `format`. Пустая строка — поле очищено. Невалидное значение, дата вне `limitRange` или дата из `disabledDays` отображаются как пустое поле. |
31
+ | `onChange` | `(value: string) => void` | Вызывается при фиксации значения. Аргумент — строка даты в формате `format` либо пустая строка при очистке поля. |
32
+ | `invalidDateHint` | `ReactNode` | Текст тултипа, который показывается, когда в поле введена полная (10 символов), но недоступная для выбора дата. |
70
33
 
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
- ```
34
+ ### Опциональные props
98
35
 
99
- Контекст существует **только для передачи callback'ов в `DateFieldTarget`**,
100
- который живёт внутри `DatePickerExtended` (другая ветка дерева). Публично
101
- не экспортируется.
36
+ | Prop | Тип | По умолчанию | Описание |
37
+ |---|---|---|---|
38
+ | `size` | `EComponentSize` | `MD` (в target) | Размер поля: SM / MD / LG. Определяет и размер иконки календаря, и `min-width` поля ввода. |
39
+ | `status` | `EFormFieldStatus` | `default` | Статус поля: default / error / warning / disabled. `disabled` блокирует поле и кнопку календаря. |
40
+ | `label` | `ReactNode` | — | Лейбл поля (проброс в `MaskedField`). |
41
+ | `placeholderMask` | `string` | — | Символы для заполнения пустых редактируемых позиций маски, например `"дд.мм.гггг"`. |
42
+ | `format` | `string` | `dateFormatYYYYMMDD` (`"YYYYMMDD"`) | Формат парсинга/сериализации `value` и аргумента `onChange`. На отображение НЕ влияет — в поле всегда показывается `"DD.MM.YYYY"`. |
43
+ | `limitRange` | `IDateLimitRange` | `globalLimitRange` | Допустимый диапазон дат. Значение вне диапазона трактуется как пустое и не фиксируется через `onChange`. |
44
+ | `disabledDays` | `string[]` | — | Недоступные для выбора дни. **Строки должны быть в формате `format`**, а не в формате отображения. |
45
+ | `onClear` | `MouseEventHandler<HTMLButtonElement>` | — | Если передан — в постфиксе появляется кнопка очистки. Реальную очистку значения выполняет потребитель. |
46
+ | `onDropdownOpen` / `onDropdownClose` | `() => void` | — | Колбэки открытия/закрытия выпадающего календаря. Вызываются после внутренней обработки: при закрытии `onChange` (фиксация введённого значения) может сработать раньше `onDropdownClose`. |
47
+ | `targetProps` | `DeepPartial<IMaskedFieldProps>` | — | Дополнительные props внутреннего `MaskedField` (`postfix`, `description`, `maskedInputProps` и т.д.). Приоритет: верхнеуровневые ключи `targetProps` **переопределяют** заданные компонентом `size` / `status` / `label` / `onClear` (`{...restTargetProps}` разворачивается после них). Значения `maskedInputProps.value`, `mask`, `placeholderMask`, `aria-label`, `aria-labelledby` — тоже лишь **значения по умолчанию**, переданные одноимённые ключи их переопределяют. **Но обработчики не переопределяются, а дополняются:** `onChange`, `onFocus`, `onBlur`, `onKeyDown`, `onMouseDown` из `maskedInputProps` вызываются ПОСЛЕ внутренних, поэтому свой `onBlur` не отключает фиксацию значения. `className` не переопределяется, а мержится через `clsx`. |
48
+
49
+ ### Логика фиксации значения
50
+
51
+ Ключевая особенность компонента: ввод с клавиатуры **не** вызывает `onChange` на каждое нажатие. Значение фиксируется, когда пользователь заканчивает редактирование:
52
+
53
+ - **При вводе** обновляется только внутренний стейт (`pickerValues`): строка поля и подсвеченная в календаре дата. Если введены все 10 символов и дата невалидна/недоступна — открывается тултип с `invalidDateHint`.
54
+ - **По blur** поля (при закрытом дропдауне) и **по закрытию дропдауна** (если поле не в фокусе) вызывается `triggerChangeFromInput`:
55
+ - поле очищено, а `value` не пустое → `onChange("")`;
56
+ - введена валидная и доступная дата, отличная от текущего `value` → `onChange(date.format(format))`;
57
+ - иначе поле откатывается к последнему валидному значению (`lastValidPickerValuesRef`).
58
+ - **Выбор даты в календаре** вызывает `onChange` сразу.
59
+
60
+ Компонент контролируемый: пока потребитель не обновит `value` в ответ на `onChange`, зафиксированное значение в поле не изменится.
102
61
 
103
- ### `DateFieldUtils`
62
+ ---
104
63
 
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
- ```
64
+ ## Дизайн-токены
112
65
 
113
- Конвертирует внешний `value` (в `format`) → внутреннее состояние
114
- (`inputString` в `inputDateFormat` + `calendarDate` Moment|null).
66
+ Собственных токенов нет — в `styles/DateFieldTarget.module.less` заданы только `min-width` поля по размерам. Внешний вид наследуется от `MaskedField` / `FormField`, `ButtonIcon`, `FormFieldClear`, `Tooltip` и `Calendar` / `DatePickerExtended`.
115
67
 
116
68
  ---
117
69
 
118
- ## Ключевые особенности реализации
70
+ ## Инварианты
119
71
 
120
- ### Два формата: `format` (внешний) vs `inputDateFormat` (внутренний)
72
+ - `forwardRef` на компоненте — не убирать. Ref пробрасывается в `DatePickerExtended` (корневой DOM-элемент — элемент `DatePickerExtended`); та же ссылка используется внутри как `targetRef` тултипа, поэтому `setRef` должен продолжать писать и в `tooltipTargetRef`.
73
+ - Публичный API (`IDateFieldProps`: `value`, `onChange`, `onClear`, `invalidDateHint`, `placeholderMask`, `targetProps` + унаследованные `size`/`status`/`label` и props `DatePickerExtended`) — изменение имён/типов/значений — breaking change.
74
+ - Отображаемый формат ввода зафиксирован константой `inputDateFormat = "DD.MM.YYYY"` (`constants.ts`) и связан с маской `FormFieldMaskedInput.presets.masks.date`. Менять его нужно синхронно с маской — иначе разъедутся длина ввода (`event.target.value.length === inputDateFormat.length`) и парсинг.
75
+ - `disabledDays` сравниваются со строкой даты в формате `format` (`isDayDisabled` делает `includes`) — не менять формат сравнения без обновления документации потребителя.
76
+ - Sync-эффект в `DateField.tsx` (`useEffect` по `[value, format, limitRange, disabledDays]` с `eslint-disable react-hooks/exhaustive-deps`) — намеренная синхронизация derived-стейта `pickerValues` с внешним `value`. `pickerValues` намеренно не в зависимостях: иначе эффект затирал бы промежуточный ввод пользователя. Сравнение идёт по `inputString`.
77
+ - Внутренние символы `DateFieldUtils` (`utils.ts`), `DateFieldContext`, `DateFieldTarget`, `inputDateFormat` не экспортируются через barrel — это приватные детали реализации. **Исключение — тип `IDateFieldTargetProps`:** он лежит в `types.ts`, а `index.ts` делает `export * from "./types"`, поэтому тип уходит в публичный API пакета. Переименование или изменение его полей — breaking change.
78
+ - **`isOpen={tooltipOpenedRef.current}` читает ref во время рендера** (`DateField.tsx`, ветка `MobileView.fallback`). Это работает только потому, что `handleInputChange` в том же вызове делает `setPickerValues` и тем самым провоцирует ре-рендер. Любая «оптимизация» вида «не вызывать `setPickerValues`, если строка не изменилась» тихо сломает показ `invalidDateHint`, при этом тесты на ввод останутся зелёными. Трогать эту пару (ref + `setPickerValues`) только вместе.
79
+ - **`DateFieldUtils` и `inputDateFormat` переиспользует `ChipDatePicker`** (`src/components/Chip/ChipDatePicker/ChipDatePicker.tsx`), который повторяет ту же логику ввода даты. Переименование или изменение сигнатур этих символов ломает `ChipDatePicker` — правь их только синхронно с ним.
121
80
 
122
- - **Внешний `value`** и `onChange` — в moment-формате, по умолчанию
123
- `YYYY-MM-DD`. Настраивается через пропс `format`.
124
- - **Внутренний ввод (маска)** — всегда `"DD.MM.YYYY"`. Захардкожен
125
- в `constants.ts`.
81
+ ---
126
82
 
127
- **Почему:** визуальный формат — фирменный российский (дд.мм.гггг), а
128
- внутренний API — ISO для backend. Конвертация через `moment(date,
129
- format)` / `date.format(inputDateFormat)`.
83
+ ## Accessibility
130
84
 
131
- ### Локальное состояние `pickerValues = { inputString, calendarDate }`
85
+ - Триггер — поле ввода с маской. Открытие/закрытие календаря по клику (`mousedown`), по клавише `Enter` и по `Space` (с `preventDefault` для `Space`, чтобы не вводить пробел и не скроллить страницу). Обработчики в `DateFieldTarget`.
86
+ - `aria-label` и `aria-labelledby` пробрасываются потребителем на элемент input через одноимённые props `DateField`. Компонент не хардкодит текст — библиотека мультиязычная.
87
+ - Состояние `disabled` (`status === EFormFieldStatus.DISABLED`) блокирует и поле, и кнопку календаря.
88
+ - Тултип с `invalidDateHint` показывается только в desktop-ветке (`MobileView.fallback`); в мобильном представлении ввод идёт через `DropdownMobileMaskedInput` в заголовке дропдауна.
89
+ - ARIA-роль выпадающего календаря (`dialog`) и навигация по нему — на стороне `DatePickerExtended` / `Calendar`.
90
+ - **Известный пробел:** кнопка-иконка календаря (`ButtonIcon` в `DateFieldTarget`) фокусируема и не имеет `aria-label` — скринридер объявит её без имени. В `MonthYearFieldTarget` аналогичная кнопка выведена из таб-обхода (`role="presentation"`, `tabIndex={-1}`). Приведение к общему поведению требует изменения публичного API (новый prop для локализованного лейбла) и вынесено за рамки AI-рефакторинга.
132
91
 
133
- ```ts
134
- const [pickerValues, setPickerValues] = useState(
135
- DateFieldUtils.getPickerValues(value, format, limitRange, disabledDays),
136
- );
137
- ```
92
+ ---
138
93
 
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
- ```
94
+ ## Связанные компоненты
151
95
 
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
- ```
96
+ - `DatePickerExtended` — база выпадающего календаря; `DateField` конфигурирует её через render-props (`renderTarget`, `renderDropdownHeaderTarget`) и наследует её props.
97
+ - `MonthYearField` — альтернатива для выбора только месяца и года; устроен по той же схеме, но поле read-only.
98
+ - `ChipDatePicker` — выбор даты в виде чипа-фильтра, а не поля формы; альтернатива, когда дата задаётся в панели фильтров. Одновременно единственный потребитель, опирающийся на приватные детали `DateField` — импортирует `DateFieldUtils` и `inputDateFormat` (см. «Инварианты»), поэтому правки этих символов проверяй и на нём.
99
+ - `MaskedField` — внутреннее поле-триггер; его props частично входят в публичный API (`size`, `status`, `label`, `targetProps`).
100
+ - `Calendar` — календарь внутри дропдауна; его props (`limitRange`, `disabledDays`, `markedDays`, `format`) входят в публичный API через `DatePickerExtended`.
101
+ - `Tooltip` — показывает `invalidDateHint` при вводе недоступной даты (только desktop-ветка).
102
+ - `DateFieldTarget` (внутренний, не в barrel) — рендерит `MaskedField` с иконкой календаря и кнопкой очистки, обрабатывает клик/клавиатуру/фокус. Описан здесь, отдельного AI.md не имеет. Покрыт `__tests__/DateFieldTarget.test.tsx`.
103
+ - `DateFieldContext` (внутренний, не в barrel) — прокидывает `onChange`, `inputFocusedRef` и `triggerChangeFromInput` в target.
104
+ - `DateFieldUtils` (`utils.ts`, внутренний) — чистые функции парсинга/валидации даты (`getPickerValues`, `getCalendarDate`, `isAvailableDate`). Покрыты `__tests__/utils.test.tsx`.
180
105
 
181
- Триггерится при blur input'а (если dropdown закрыт) и при закрытии
182
- dropdown'а (если input был расфокусирован и грязный). **Логика "не дать
183
- пользователю повесить невалидную дату"** — UX-решение.
106
+ ---
184
107
 
185
- ### Tooltip с `invalidDateHint` через ref
108
+ ## Stories
186
109
 
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
- ```
110
+ Основные истории: `stories/DateField/DateField.stories.tsx`
111
+ Файлы примеров: `stories/DateField/examples/`
199
112
 
200
- Tooltip появляется **только на десктопе** и **только при `tooltipOpened.current === true`**
201
- (= длина ввода = длина маски, но дата невалидна). На мобильных — нет
202
- tooltip'а, нет подсказки; валидация через собственный UI потребителя.
113
+ | Story | Example file | Что демонстрирует |
114
+ |---|---|---|
115
+ | `Playground` | `PlaygroundExample.tsx` | Интерактивный контроль size / status / label / placeholderMask / invalidDateHint + настройки postfix и description |
116
+ | `Default` | `DefaultExample.tsx` | Минимальное поле с label и маской-плейсхолдером |
117
+ | `Sizes` | `SizesExample.tsx` | Размеры SM / MD / LG |
118
+ | `Statuses` | `StatusesExample.tsx` | Статусы default / disabled / error / warning |
119
+ | `Production` | `ProductionExample.tsx` | Production-композиция (`Example: production`): postfix `HelpBox`, description со ссылкой, кнопка очистки и возврат фокуса на input |
120
+ | `VisualTests` | `VisualTestsExample.tsx` | Скриншот-регрессия: заполненные поля всех размеров; `play` кликает по последнему полю, раскрывая календарь |
203
121
 
204
- ### Mobile-ветка: `DropdownMobileMaskedInput`
122
+ ---
205
123
 
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
- ```
124
+ ## История изменений
217
125
 
218
- На мобильных `DatePickerExtended` открывается как Dropdown с **инпутом
219
- в заголовке** (не отдельным target'ом). `autoFocus={true}` ставит курсор
220
- в поле при открытии.
126
+ | Дата | Изменение |
127
+ |---|---|
128
+ | 2026-09-08 | Создан документ. AI-рефакторинг (JSDoc-фиксы, `displayName` у `DateFieldTarget`), unit-тесты для `utils.ts`, поведения поля (ввод, blur, клавиатура, колбэки дропдауна) и `DateFieldTarget` (фокус, mousedown, клавиатура, проброс обработчиков) |
221
129
 
222
- ### `DateFieldTarget` живёт в чужой ветке дерева
130
+ ---
223
131
 
224
- `DateFieldTarget` рендерится внутри `DatePickerExtended` (target-prop).
225
- Он не прямой потомок `DateField`, поэтому **обмен с корнем идёт через
226
- `DateFieldContext`** (inputFocusedRef, onChange, triggerChangeFromInput).
132
+ ## Примеры использования
227
133
 
228
- **Это известная архитектурная особенность**: Target — это HOC над
229
- MaskedField + calendar icon + clear button + keyboard (`Enter`/`Space`
230
- открывают dropdown).
134
+ ### Default
231
135
 
232
- ### `setRef` — double-duty ref
136
+ `stories/DateField/examples/DefaultExample.tsx`
233
137
 
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;
138
+ ```tsx
139
+ import React, { useState } from "react";
140
+ import { DateField } from "@sberbusiness/triplex-next";
141
+
142
+ export const DefaultExample = () => {
143
+ const [value, setValue] = useState("");
144
+
145
+ return (
146
+ <div style={{ maxWidth: 300 }}>
147
+ <DateField
148
+ value={value}
149
+ label="Label"
150
+ placeholderMask="дд.мм.гггг"
151
+ invalidDateHint="Указана недоступная для выбора дата."
152
+ onChange={setValue}
153
+ />
154
+ </div>
155
+ );
239
156
  };
240
157
  ```
241
158
 
242
- Один и тот же DOM-элемент используется:
243
- 1. Как target для Tooltip (`tooltipTargetRef`).
244
- 2. Как forwarded ref для потребителя.
159
+ ### Sizes
245
160
 
246
- ### `dropdownClosedByCalendarRef` — флаг "закрыто по выбору даты"
161
+ `stories/DateField/examples/SizesExample.tsx`
247
162
 
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: клавиатурная навигация
163
+ ```tsx
164
+ import React, { useState } from "react";
165
+ import { DateField, EComponentSize } from "@sberbusiness/triplex-next";
269
166
 
270
- ```ts
271
- if (isKey(event.code, "ENTER")) setDropdownOpen(!dropdownOpen);
272
- else if (isKey(event.code, "SPACE")) {
273
- event.preventDefault();
274
- setDropdownOpen(!dropdownOpen);
167
+ interface ISizeItemProps {
168
+ size: EComponentSize;
275
169
  }
276
- ```
277
-
278
- **Enter / Space** открывают/закрывают dropdown. `preventDefault` на
279
- Space нужен, чтобы не ввести пробел в input (маска бы его съела,
280
- но всё равно нежелательно).
281
-
282
- ---
283
170
 
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
- документацию).
171
+ const SizeItem = ({ size }: ISizeItemProps) => {
172
+ const [value, setValue] = useState<string>("");
173
+
174
+ return (
175
+ <div style={{ maxWidth: 300 }}>
176
+ <div style={{ marginBottom: 8, fontSize: 16, fontWeight: 700 }}>{size.toUpperCase()}</div>
177
+ <DateField
178
+ size={size}
179
+ value={value}
180
+ label="Label"
181
+ placeholderMask="дд.мм.гггг"
182
+ invalidDateHint="Указана недоступная для выбора дата."
183
+ onChange={setValue}
184
+ />
185
+ </div>
186
+ );
187
+ };
321
188
 
322
- ---
189
+ const SIZES = Object.values(EComponentSize);
323
190
 
324
- ## Дизайн-токены
191
+ export const SizesExample = () => (
192
+ <div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
193
+ {SIZES.map((size) => (
194
+ <SizeItem key={size} size={size} />
195
+ ))}
196
+ </div>
197
+ );
198
+ ```
325
199
 
326
- **Нет собственных.** Все визуальные токены — от `MaskedField` (fonts,
327
- colors, borders, focus-ring) и `DatePickerExtended` (dropdown, calendar).
200
+ ### Statuses
328
201
 
329
- ---
202
+ `stories/DateField/examples/StatusesExample.tsx`
330
203
 
331
- ## Связанные компоненты
204
+ ```tsx
205
+ import React, { useState } from "react";
206
+ import { DateField, EFormFieldStatus } from "@sberbusiness/triplex-next";
332
207
 
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` — кнопка-иконка календаря.
208
+ interface IStatusItemProps {
209
+ status: EFormFieldStatus;
210
+ }
348
211
 
349
- ---
212
+ const StatusItem = ({ status }: IStatusItemProps) => {
213
+ const [value, setValue] = useState<string>("");
214
+
215
+ return (
216
+ <div style={{ maxWidth: 300 }}>
217
+ <div style={{ marginBottom: 8, fontSize: 16, fontWeight: 700 }}>{status.toUpperCase()}</div>
218
+ <DateField
219
+ status={status}
220
+ value={value}
221
+ label="Label"
222
+ placeholderMask="дд.мм.гггг"
223
+ invalidDateHint="Указана недоступная для выбора дата."
224
+ onChange={setValue}
225
+ />
226
+ </div>
227
+ );
228
+ };
350
229
 
351
- ## Stories
230
+ const STATUSES = Object.values(EFormFieldStatus);
352
231
 
353
- `stories/DateField/DateField.stories.tsx`
354
- Примеры: `stories/DateField/examples/`
232
+ export const StatusesExample = () => (
233
+ <div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
234
+ {STATUSES.map((status) => (
235
+ <StatusItem key={status} status={status} />
236
+ ))}
237
+ </div>
238
+ );
239
+ ```
355
240
 
356
- Типовые stories:
357
- - `Default` — базовое поле
358
- - `WithLabel` — с label
359
- - `Sizes` — SM/MD/LG
360
- - `Statuses` — default/error/success/disabled
361
- - `LimitRange` — ограниченный диапазон
362
- - `DisabledDays` — выборочно отключённые даты
363
- - `Playground` — интерактив
241
+ ### Production
364
242
 
365
- ---
243
+ `stories/DateField/examples/ProductionExample.tsx`
366
244
 
367
- ## История изменений
245
+ ```tsx
246
+ import React, { useState, useRef } from "react";
247
+ import { DateField, HelpBox, Text, Link, ETooltipSize, ETextSize, EFontType } from "@sberbusiness/triplex-next";
248
+
249
+ export const ProductionExample = () => {
250
+ const [value, setValue] = useState("");
251
+ const inputRef = useRef<HTMLInputElement>(null);
252
+
253
+ const handleLinkClick: React.MouseEventHandler<HTMLAnchorElement> = (event) => {
254
+ event.preventDefault();
255
+ };
256
+
257
+ const handleClear: React.MouseEventHandler<HTMLButtonElement> = () => {
258
+ setValue("");
259
+ inputRef.current?.focus();
260
+ };
261
+
262
+ return (
263
+ <div style={{ maxWidth: 300 }}>
264
+ <DateField
265
+ value={value}
266
+ label="Label"
267
+ placeholderMask="дд.мм.гггг"
268
+ invalidDateHint="Указана недоступная для выбора дата."
269
+ onChange={setValue}
270
+ onClear={handleClear}
271
+ targetProps={{
272
+ maskedInputProps: {
273
+ forwardedRef: inputRef,
274
+ },
275
+ postfix: <HelpBox tooltipSize={ETooltipSize.SM}>Helpful details appear here</HelpBox>,
276
+ description: (
277
+ <Text size={ETextSize.B4} type={EFontType.SECONDARY}>
278
+ (21) Description{" "}
279
+ <Link href="#" onClick={handleLinkClick}>
280
+ Link text
281
+ </Link>
282
+ </Text>
283
+ ),
284
+ }}
285
+ />
286
+ </div>
287
+ );
288
+ };
289
+ ```
368
290
 
369
- | Дата | Изменение |
370
- |---|---|
371
- | 2026-04-18 | Создан документ |