@sberbusiness/triplex-next-mcp-server 1.46.0-beta → 1.48.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 (48) hide show
  1. package/README.md +7 -7
  2. package/bundle/components/AmountField.md +222 -323
  3. package/bundle/components/Avatar.md +138 -146
  4. package/bundle/components/CardStatic.md +402 -0
  5. package/bundle/components/CheckboxTree.md +157 -215
  6. package/bundle/components/CheckboxTreeExtended.md +7 -7
  7. package/bundle/components/ChipGroup.md +1 -1
  8. package/bundle/components/ChipMonthYearPicker.md +4 -4
  9. package/bundle/components/ChipSuggest.md +7 -6
  10. package/bundle/components/CollapsibleTree.md +8 -4
  11. package/bundle/components/CollapsibleTreeExtended.md +8 -6
  12. package/bundle/components/Confirm.md +346 -281
  13. package/bundle/components/DateRange.md +351 -210
  14. package/bundle/components/FooterPage.md +1 -0
  15. package/bundle/components/FormField.md +11 -4
  16. package/bundle/components/FormGroup.md +3 -2
  17. package/bundle/components/LightBox.md +8 -0
  18. package/bundle/components/ListMaster.md +2 -2
  19. package/bundle/components/ListTableItem.md +6 -6
  20. package/bundle/components/MonthYearField.md +15 -4
  21. package/bundle/components/Slider.md +389 -97
  22. package/bundle/components/SliderRange.md +486 -105
  23. package/bundle/components/StatusTracker.md +386 -94
  24. package/bundle/components/Stepper.md +355 -102
  25. package/bundle/components/StepperExtended.md +633 -0
  26. package/bundle/components/SuggestField.md +1049 -96
  27. package/bundle/components/SwipeableArea.md +7 -7
  28. package/bundle/components/Tabs.md +265 -92
  29. package/bundle/components/TabsExtended.md +18 -18
  30. package/bundle/components/TabsLine.md +301 -127
  31. package/bundle/components/TagColor.md +187 -66
  32. package/bundle/components/TagGroup.md +184 -48
  33. package/bundle/components/TextareaField.md +278 -70
  34. package/bundle/components/TopOverlay.md +17 -1
  35. package/bundle/components/TreeView.md +16 -7
  36. package/bundle/components/TriggerClickOnKeyDownEvent.md +277 -0
  37. package/bundle/components/UploadZone.md +318 -0
  38. package/bundle/guides/codestyle.md +16 -1
  39. package/bundle/guides/commits.md +47 -1
  40. package/bundle/guides/tests.md +23 -2
  41. package/bundle/manifest.json +48 -21
  42. package/bundle/release-notes/1.47.0.mdx +115 -0
  43. package/bundle/release-notes/1.48.0.mdx +68 -0
  44. package/bundle/release-notes/1.49.0.mdx +7 -0
  45. package/bundle/tokens.json +3 -3
  46. package/dist/index.js +220 -6
  47. package/dist/index.js.map +1 -1
  48. package/package.json +1 -1
@@ -1,9 +1,7 @@
1
1
  ---
2
2
  component: DateRange
3
- category: Form
4
- figma-node: ""
5
- figma-file: ""
6
- related: [DateField, DatePickerExtended, Calendar, MonthYearField]
3
+ category: Date components
4
+ related: [MonthYearRange, DateField, ButtonIcon]
7
5
  tokens: []
8
6
  stories: stories/DateRange/DateRange.stories.tsx
9
7
  version: "1.0"
@@ -13,258 +11,126 @@ version: "1.0"
13
11
 
14
12
  ## Назначение
15
13
 
16
- Layout-контейнер для выбора **диапазона дат** (от … до) с опциональными
17
- кнопками "сдвинуть диапазон назад/вперёд" на фиксированную величину
18
- (день/неделя/месяц/квартал/год). Сам **не рендерит пикеры** — принимает
19
- их через render-props (`renderPickerFrom`, `renderPickerTo`,
20
- `renderButtonBack`, `renderButtonForward`), потребитель обычно
21
- передаёт два `DateField`.
14
+ Диапазон дат: два поля выбора даты («от» и «до») и кнопки сдвига всего диапазона назад и вперёд. Сам компонент ни полей, ни кнопок не рендерит — их отдаёт потребитель через render-props (`renderPickerFrom`, `renderPickerTo`, `renderButtonBack`, `renderButtonForward`). На DateRange остаются раскладка, согласование границ диапазона (дата «от» не может быть больше даты «до») и арифметика сдвига.
22
15
 
23
- Автоматически **валидирует пересечение**: если пользователь поставил
24
- `from > to`, компонент очищает `to` (или наоборот очищает `from`, если
25
- пользователь ставит `to < from`).
16
+ Используй когда: пользователю нужно ввести период двумя полями дат и быстро перелистывать его целиком (предыдущий месяц, следующий квартал).
26
17
 
27
- Используй когда: нужен диапазон дат в форме (например, фильтр периода).
28
- Не используй когда: нужна **одна** дата — бери `DateField`; нужен выбор
29
- одного **периода** (месяц/квартал) — `MonthYearField`.
30
-
31
- ---
32
-
33
- ## Файловая структура
34
-
35
- ```text
36
- src/components/DateRange/
37
- ├── DateRange.tsx # FC, НЕ forwardRef
38
- ├── enums.ts # EDateRangeShiftUnit (day/week/month/quarter/year)
39
- ├── index.ts # Barrel
40
- ├── styles/DateRange.module.less # .dateRange flex + .separator 20x20 + .dateRangeButton z-index: 1
41
- └── __tests__/DateRange.test.tsx
42
- ```
43
-
44
- Особенности:
45
- - **НЕ `forwardRef`** — FC. Ref-форвардинг не реализован. Известный
46
- AI-ready debt.
47
- - **Render-props-first** — `renderPickerFrom`, `renderPickerTo`,
48
- `renderButtonBack`, `renderButtonForward` — все 4 обязательны.
49
- - **`moment.js`** для арифметики сдвига диапазона.
50
- - **Тесты есть** — `__tests__/DateRange.test.tsx` покрывает валидацию
51
- пересечения и сдвиги.
18
+ Не используй когда: нужен диапазон месяцев, а не дней — `MonthYearRange`; нужна одна дата — `DateField`; нужен собственный триггер с выпадающим календарём — `DatePickerExtended`; нужна календарная сетка без полей — `Calendar`.
52
19
 
53
20
  ---
54
21
 
55
22
  ## Варианты и props
56
23
 
57
- ### `IDateRangeProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "defaultValue" | "onChange">`
58
-
59
- | Prop | Тип | Default | Описание |
60
- |---|---|---|---|
61
- | `value` | `TDateRangeValue = [string, string]` | — | Диапазон `[from, to]` в формате `YYYY-MM-DD`. |
62
- | `onChange` | `(value: [string, string]) => void` | — | Вызывается при изменении любой даты или при сдвиге. |
63
- | `shiftAmount` | `number` | `1` | Величина сдвига диапазона. |
64
- | `shiftUnit` | `EDateRangeShiftUnit` | `MONTH` | Единица сдвига (`"day" \| "week" \| "month" \| "quarter" \| "year"` — значения moment-совместимы). |
65
- | `hideNavigation` | `boolean` | `false` | Скрыть кнопки сдвига. |
66
- | `renderPickerFrom` | `(props: IDateRangePickerProvideProps) => ReactNode` | — | Обязателен. Рендерит поле "от". |
67
- | `renderPickerTo` | `(props: IDateRangePickerProvideProps) => ReactNode` | — | Обязателен. Рендерит поле "до". |
68
- | `renderButtonBack` | `(props: IDateRangeButtonProvideProps) => ReactNode` | — | Обязателен. Рендерит кнопку "назад". |
69
- | `renderButtonForward` | `(props: IDateRangeButtonProvideProps) => ReactNode` | — | Обязателен. Рендерит кнопку "вперёд". |
70
- | `className`, `...HTMLDivAttributes` | — | — | Стандартные. |
71
-
72
- ### Provide-props
73
-
74
- **`IDateRangePickerProvideProps`:**
75
- ```ts
76
- { value: string; onChange: (value: string) => void; }
77
- ```
78
-
79
- **`IDateRangeButtonProvideProps`:**
80
- ```ts
81
- {
82
- children: ReactNode; // стрелка-иконка Caretleft/rightStrokeSrvIcon20
83
- className: string; // .dateRangeButton + .disabled при пустом диапазоне
84
- onClick: () => void; // shiftRangeBack / shiftRangeForward
85
- disabled: boolean; // true если start или end пустые
86
- }
87
- ```
88
-
89
- ### `TDateRangeValue = [string, string]`
90
-
91
- Кортеж из двух строк. Пустая строка = "дата не задана". Это важно: **не
92
- `undefined`, а именно `""`**. Потребитель должен всегда передавать
93
- массив длиной 2.
24
+ `IDateRangeProps` наследует `React.HTMLAttributes<HTMLDivElement>` без `defaultValue` и `onChange`.
94
25
 
95
- ### `EDateRangeShiftUnit`
26
+ ### Обязательные props
96
27
 
97
- ```ts
98
- enum EDateRangeShiftUnit {
99
- DAY = "day",
100
- WEEK = "week",
101
- MONTH = "month",
102
- QUARTER = "quarter",
103
- YEAR = "year",
104
- }
105
- ```
106
-
107
- Значения — строки, совместимые с `moment().add(n, unit)`.
108
-
109
- ---
110
-
111
- ## Ключевые особенности реализации
112
-
113
- ### Валидация пересечения: очистка при инверсии
114
-
115
- ```ts
116
- const handleChangePickerFrom = (date: string) => {
117
- if (!date || !end || date <= end) {
118
- onChange([date, end]);
119
- } else {
120
- onChange([date, ""]); // from > to → чистим to
121
- }
122
- };
123
-
124
- const handleChangePickerTo = (date: string) => {
125
- if (!date || !start || date >= start) {
126
- onChange([start, date]);
127
- } else {
128
- onChange(["", date]); // to < from → чистим from
129
- }
130
- };
131
- ```
132
-
133
- **Важная деталь:** сравнение `date <= end` / `date >= start` — это
134
- **лексикографическое** сравнение строк. Работает **только** для формата
135
- `YYYY-MM-DD` (ISO). Для других форматов (`DD.MM.YYYY` и т. п.) сравнение
136
- сломается.
137
-
138
- **Инвариант:** `value` и всё, что DateRange возвращает через `onChange`,
139
- строго в `YYYY-MM-DD`. Потребитель отвечает за форматирование в
140
- собственных пикерах (`DateField` сам конвертирует через `format` проп,
141
- но по умолчанию тоже `YYYY-MM-DD`).
142
-
143
- ### Сдвиг диапазона — `moment` арифметика
144
-
145
- ```ts
146
- const shiftRangeBack = () => {
147
- if (!start || !end) return;
148
- const momentStart = moment(start, dateFormatYYYYMMDD);
149
- const momentEnd = moment(end, dateFormatYYYYMMDD);
150
- onChange([
151
- momentStart.subtract(shiftAmount, shiftUnit).format(dateFormatYYYYMMDD),
152
- momentEnd.subtract(shiftAmount, shiftUnit).format(dateFormatYYYYMMDD),
153
- ]);
154
- };
155
- ```
28
+ | Prop | Тип | Описание |
29
+ |---|---|---|
30
+ | `value` | `TDateRangeValue` | Кортеж `[start, end]`. Компонент управляемый — состояние держит потребитель. |
31
+ | `onChange` | `(value: TDateRangeValue) => void` | Вызывается при изменении любой из границ и при сдвиге диапазона. Всегда получает обе даты. |
32
+ | `renderPickerFrom` | `(props: IDateRangePickerProvideProps) => ReactNode` | Рендер поля даты «от». |
33
+ | `renderPickerTo` | `(props: IDateRangePickerProvideProps) => ReactNode` | Рендер поля даты «до». |
34
+ | `renderButtonBack` | `(props: IDateRangeButtonProvideProps) => ReactNode` | Рендер кнопки сдвига назад. Вызывается только при `hideNavigation !== true`. |
35
+ | `renderButtonForward` | `(props: IDateRangeButtonProvideProps) => ReactNode` | Рендер кнопки сдвига вперёд. Вызывается только при `hideNavigation !== true`. |
156
36
 
157
- **Защита:** если start или end пустые → no-op. Кнопка тоже
158
- `disabled={!(start && end)}`.
37
+ ### Опциональные props
159
38
 
160
- **Сдвигаются обе границы на одно и то же значение** — не "растянуть
161
- диапазон", а "сдвинуть целиком". Размер диапазона сохраняется.
39
+ | Prop | Тип | По умолчанию | Описание |
40
+ |---|---|---|---|
41
+ | `shiftAmount` | `number` | `1` | На сколько единиц `shiftUnit` сдвигаются обе границы за одно нажатие. |
42
+ | `shiftUnit` | `EDateRangeShiftUnit` | `MONTH` | Единица сдвига: `DAY`, `WEEK`, `MONTH`, `QUARTER`, `YEAR`. Значения enum — строки, которые напрямую уходят в `moment`. |
43
+ | `hideNavigation` | `boolean` | `false` | Скрывает обе кнопки сдвига: render-props кнопок не вызываются вовсе. |
162
44
 
163
- **Граничный случай:** если `shiftUnit=MONTH` и `start=2026-01-31`, то
164
- `moment.subtract(1, 'month') = 2025-12-31` (moment сам обрабатывает
165
- конец месяца). Для шага `1 year + 29 feb` — может быть скачок на 1
166
- день. Это поведение `moment` и осознанное допущение.
45
+ ### Формат значения
167
46
 
168
- ### Separator — `MinusStrokeSrvIcon20`
47
+ `TDateRangeValue` — `[start, end]`, обе даты строками в формате `YYYYMMDD` (`dateFormatYYYYMMDD` из `src/consts/DateConst`). Пустая строка означает «дата не выбрана»; допустимо состояние, когда заполнена только одна граница.
169
48
 
170
- Между двумя пикерами рендерится иконка-минус 20x20 (`flex-shrink: 0`,
171
- чтобы не сжималась).
49
+ ### Что получают render-props
172
50
 
173
- ### Кнопки сдвига — позиция z-index: 1
51
+ | Поле | Кому | Значение |
52
+ |---|---|---|
53
+ | `value` | пикеры | Соответствующая граница диапазона (`start` или `end`). |
54
+ | `onChange` | пикеры | Принимает строку `YYYYMMDD`; внутри применяется согласование границ (ниже). |
55
+ | `children` | кнопки | Иконка направления (`CaretleftStrokeSrvIcon20` / `CaretrightStrokeSrvIcon20`). |
56
+ | `className` | кнопки | Класс кнопки из LESS-модуля; при неполном диапазоне к нему добавляется литерал `disabled` (см. «Инварианты»). |
57
+ | `disabled` | кнопки | `true`, пока заполнены не обе границы. Применить его к DOM-элементу — задача потребителя. |
58
+ | `onClick` | кнопки | Сдвиг диапазона. На неполном диапазоне обработчик выходит, не вызывая `onChange`. |
174
59
 
175
- ```less
176
- .dateRangeButton { z-index: 1; }
177
- ```
60
+ Объекты сформированы так, чтобы их можно было целиком спредить в `DateField` (пикеры) и `ButtonIcon` (кнопки) — именно так устроены все примеры в stories.
178
61
 
179
- **Зачем:** в макете кнопки могут визуально перекрываться с soft-edge
180
- полями пикеров (Dropdown может показываться поверх). z-index: 1 даёт
181
- кнопкам приоритет над содержимым пикера при совпадении контекста.
182
- **Не удалять** — без этого кнопки могут "уйти под" пикер.
62
+ ### Что уходит на корневой элемент
183
63
 
184
- ### `disabled` = нет диапазона (пусто)
64
+ `className` объединяется с внутренним классом; остальные HTML-атрибуты (`id`, `style`, `data-*`, `aria-*`, обработчики) спредятся на корневой `div`. `children` компонент принимает по типу (от `HTMLAttributes`), но игнорирует: содержимое корневого `div` полностью определяется render-props.
185
65
 
186
- Кнопки сдвига становятся `disabled`, пока не выбраны **обе** даты.
187
- Логично — не можем сдвинуть "не определённый" диапазон.
66
+ ### Согласование границ
188
67
 
189
- ### `hideNavigation` — скрывает кнопки, не сам сдвиг
68
+ - Новая дата «от» больше текущей «до» → `onChange([date, ""])`: дата «до» сбрасывается.
69
+ - Новая дата «до» меньше текущей «от» → `onChange(["", date])`: дата «от» сбрасывается.
70
+ - Если новая дата пустая или противоположная граница пустая, проверка порядка не выполняется — значение записывается как есть.
71
+ - Сравнение строковое, без `moment`: формат `YYYYMMDD` лексикографически совпадает с хронологическим порядком. Поэтому формат дат менять нельзя (см. «Инварианты»).
190
72
 
191
- Внешний `shiftRange*` не публичные методы — доступ к сдвигам есть
192
- только через UI-кнопки. Если `hideNavigation=true`, сдвиги недоступны.
73
+ ### Сдвиг диапазона
193
74
 
194
- Если нужны **кастомные триггеры сдвига** (например, кнопки в другом
195
- месте UI), это не поддерживается — потребитель должен сам арифметику
196
- `moment` реализовать и пушить новое значение.
75
+ - Обе границы сдвигаются на одну и ту же величину (`shiftDateRange` в `utils.ts`, под капотом `moment().add()`). При сдвиге на месяцы, кварталы и годы число месяца клампится по длине целевого месяца, поэтому длина диапазона в днях может измениться: `20240331` минус месяц → `20240229`.
76
+ - Сдвиг доступен только при обеих заполненных границах: иначе кнопкам приходит `disabled: true`, а обработчик дополнительно выходит без `onChange`.
77
+ - Валидность дат перед сдвигом не проверяется: строка не в формате `YYYYMMDD` даст `"Invalid date"` в результате. Близнец `MonthYearRange` в том же месте делает строгий `moment(..., true).isValid()` и выходит без `onChange` — эталон, если поведение решат выровнять.
197
78
 
198
- ### Нет forwardRef — известный debt
79
+ ---
199
80
 
200
- `React.FC` без ref-forwarding. Потребителю ref недоступен. Если нужен —
201
- оборачивать в собственный div с ref (deviates от паттерна).
81
+ ## Дизайн-токены
202
82
 
203
- ### `data-tx={process.env.npm_package_version}` — **нет**
83
+ Собственных дизайн-токенов у компонента нет — LESS-модуль описывает только раскладку (`display: flex`, отступы разделителя и кнопок, `z-index` кнопки). Внешний вид задают компоненты, которые потребитель вернул из render-props, и цвета иконок (`paletteIndex={5}`).
204
84
 
205
- Этот компонент **не** ставит data-tx. Исключение из общего правила
206
- библиотеки. Возможно debt — при ревизии может быть добавлен.
85
+ Вертикальный отступ разделителя и кнопок подбирается под размер поля селекторами `:has([class*="sm"])`, `:has([class*="md"])`, `:has([class*="lg"])` — по подстроке в классе вложенного поля, а не по prop компонента.
207
86
 
208
87
  ---
209
88
 
210
89
  ## Инварианты
211
90
 
212
- - **`value` и `onChange` — в `YYYY-MM-DD`** (`dateFormatYYYYMMDD`).
213
- Лексикографическое сравнение не будет работать в других форматах.
214
- - **Все 4 render-props обязательны** — `renderPickerFrom`, `renderPickerTo`,
215
- `renderButtonBack`, `renderButtonForward`.
216
- - **Инверсия диапазона очищает противоположную границу** —
217
- `from > to` → чистим `to`; `to < from` → чистим `from`. Не менять.
218
- - **Сдвиг блокируется при пустом диапазоне** — `!start || !end` → no-op +
219
- `disabled: true`.
220
- - **`shiftUnit` значения совместимы с moment** — не добавлять значения,
221
- не поддерживаемые `moment.add/subtract`.
222
- - **Separator — иконка минуса 20x20** — не заменять на "–" текст
223
- (не масштабируется).
224
- - **`.dateRangeButton { z-index: 1 }`** — не удалять.
225
- - **НЕТ forwardRef** — известный debt, не добавлять точечно.
226
- - **НЕТ data-tx** — наследованный пропуск.
91
+ - **`forwardRef` обязателен.** `ref` уходит на корневой `<div>` — туда же, куда `className` и остальные html-атрибуты. Убирать `forwardRef` нельзя.
92
+ - **К `className` кнопок при неполном диапазоне подмешивается литерал `disabled`**, а не класс LESS-модуля (класса `.disabled` в модуле нет). Строка уходит наружу, в `className` компонента потребителя, и на неё опираются тесты, поэтому «починка» на `styles.disabled` — изменение наблюдаемого поведения. `MonthYearRange` в том же месте ничего не подмешивает.
93
+ - **Формат дат `YYYYMMDD` зашит в контракт**: на нём держится строковое сравнение границ и парсинг в `moment`. Ни `value`, ни аргумент `onChange` пикера в другом формате работать не будут.
94
+ - Значения `EDateRangeShiftUnit` — это строковые единицы `moment` (`day`, `week`, `month`, `quarter`, `year`); переименование значений ломает и публичный API, и сдвиг.
95
+ - Публичный API (`DateRange`, `IDateRangeProps`, `IDateRangeButtonProvideProps`, `IDateRangePickerProvideProps`, `TDateRangeValue`, `EDateRangeShiftUnit`) экспортируется через `src/components/DateRange/index.ts`; `TDateRangeValue` живёт в `types.ts`, остальное — в `DateRange.tsx` и `enums.ts`. `utils.ts` внутренний и в barrel не экспортируется.
96
+ - Отступы завязаны на подстроки `sm` / `md` / `lg` в классе вложенного поля — переименование классов размеров в `FormField`-полях тихо сломает выравнивание.
97
+ - Компонент не хардкодит текст: доступные имена полей и кнопок задаёт потребитель — библиотека мультиязычная.
98
+ - React 17-совместимость (ветка `release-0`): не переводить на `useId` и другие React 18-only API.
227
99
 
228
100
  ---
229
101
 
230
102
  ## Accessibility
231
103
 
232
- - **Кнопки "назад"/"вперёд"** — потребитель отвечает за `aria-label`
233
- (через render-props). DateRange не задаёт `aria-label`.
234
- - **`disabled`** — передаётся в render-props, обычно попадает на
235
- `<button disabled>`.
236
- - **Separator** — декоративная иконка, screen reader её не озвучит
237
- (SVG без `role`/`aria-label`).
238
- - **Связь полей from-to** — ARIA связи не выставлены. Если нужна
239
- явная связь "это пара полей" — обёртывать в `<fieldset><legend>`.
240
-
241
- ---
242
-
243
- ## Дизайн-токены
244
-
245
- **Нет.** Layout-компонент, разметка flex + фиксированный separator.
104
+ - Собственной роли и aria-атрибутов у компонента нет: корневой `div` — только контейнер раскладки. Всё, что передано в `aria-*`, уходит на него.
105
+ - Кнопки сдвига содержат только иконку, поэтому доступное имя (`aria-label` или `title`) обязан задать потребитель в своём `renderButtonBack` / `renderButtonForward`. Компонент передаёт в них лишь иконку, класс, `disabled` и `onClick`.
106
+ - `disabled` приходит в render-prop значением — если потребитель не применит его к DOM-кнопке, она останется фокусируемой и кликабельной (клик при этом ничего не изменит).
107
+ - Связь «от» / «до», подписи и сообщения об ошибке — на стороне полей (`DateField`), собственной валидации и `aria-live` у DateRange нет.
108
+ - Клавиатурной навигации компонент не добавляет: порядок табуляции определяется DOM-порядком (кнопка назад → поле «от» → поле «до» → кнопка вперёд).
246
109
 
247
110
  ---
248
111
 
249
112
  ## Связанные компоненты
250
113
 
251
- - `DateField` (`src/components/DateField`) — типичный `renderPickerFrom`/`To`.
252
- - `DatePickerExtended` — низкоуровневый пикер.
253
- - `MonthYearField` — близнец, но для выбора отдельного периода.
254
- - `Button`/`ButtonIcon` — типично для `renderButtonBack`/`Forward`.
114
+ - `MonthYearRange` — тот же сценарий для диапазона месяцев: одинаковый набор props и render-props, отличается единицами сдвига (`EMonthYearRangeShiftUnit`).
115
+ - `DateField` — поле, ради которого сделан контракт `IDateRangePickerProvideProps`: объект пикера спредится в `DateField` целиком (`value` + `onChange`).
116
+ - `ButtonIcon` — кнопка, ради которой сделан контракт `IDateRangeButtonProvideProps`: объект кнопки спредится в `ButtonIcon` целиком (`children` + `className` + `disabled` + `onClick`).
117
+
118
+ `DatePickerExtended` и `Calendar` в DateRange не используются напрямую — они находятся внутри `DateField`.
255
119
 
256
120
  ---
257
121
 
258
122
  ## Stories
259
123
 
260
- `stories/DateRange/DateRange.stories.tsx`
261
- Примеры: `stories/DateRange/examples/`
124
+ Основные истории: `stories/DateRange/DateRange.stories.tsx`
125
+ Файлы примеров: `stories/DateRange/examples/`
262
126
 
263
- Типовые stories:
264
- - `Default` — два DateField + кнопки сдвига
265
- - `HideNavigation` — без кнопок
266
- - `ShiftUnits` — перебор DAY/WEEK/MONTH/QUARTER/YEAR
267
- - `Playground` — интерактив
127
+ | Story | Example file | Что демонстрирует |
128
+ |---|---|---|
129
+ | `Playground` | `PlaygroundExample.tsx` | Интерактивный контроль `shiftAmount` / `shiftUnit` / `hideNavigation` |
130
+ | `Default` | `DefaultExample.tsx` | Минимальный пример: два `DateField` и два `ButtonIcon`, сдвиг на месяц |
131
+ | `Sizes` | `SizesExample.tsx` | Размеры полей SM / MD / LG и выравнивание разделителя с кнопками |
132
+ | `WithoutNavigation` | `WithoutNavigationExample.tsx` | `hideNavigation`: только поля, кнопки не рендерятся |
133
+ | `Example` | `ProductionExample.tsx` | Production-композиция (`Example: production`): поля с описанием и ссылкой под ними |
268
134
 
269
135
  ---
270
136
 
@@ -272,4 +138,279 @@ const shiftRangeBack = () => {
272
138
 
273
139
  | Дата | Изменение |
274
140
  |---|---|
275
- | 2026-04-18 | Создан документ |
141
+ | 2026-09-18 | Создан документ. Компонент переведён с `React.FC` на `forwardRef`: `ref` уходит на корневой `<div>`, тип стал `React.ForwardRefExoticComponent`. AI-рефакторинг: логика сдвига вынесена в `utils.ts` (`shiftDateRange`), добавлены JSDoc и unit-тесты на хелпер. Остальной публичный API и поведение не изменились |
142
+ | 2026-09-18 | По итогам ревью: обработчики сдвига сведены в один `shiftRange(amount)`; `TDateRangeValue` вынесен в `types.ts` и реэкспортируется из `index.ts` (набор публичных имён не изменился) |
143
+
144
+ ---
145
+
146
+ ## Примеры использования
147
+
148
+ ### Default
149
+
150
+ `stories/DateRange/examples/DefaultExample.tsx`
151
+
152
+ ```tsx
153
+ import React, { useState } from "react";
154
+ import {
155
+ ButtonIcon,
156
+ DateField,
157
+ DateRange,
158
+ EDateRangeShiftUnit,
159
+ IDateRangeButtonProvideProps,
160
+ IDateRangePickerProvideProps,
161
+ TDateRangeValue,
162
+ FormFieldMaskedInput,
163
+ } from "@sberbusiness/triplex-next";
164
+
165
+ export const DefaultExample = () => {
166
+ const [value, setValue] = useState<TDateRangeValue>(["", ""]);
167
+ const renderPicker = (props: IDateRangePickerProvideProps) => (
168
+ <DateField
169
+ label="Label"
170
+ placeholderMask={FormFieldMaskedInput.presets.placeholderMasks.date}
171
+ invalidDateHint="Указана недоступная для выбора дата."
172
+ {...props}
173
+ />
174
+ );
175
+ const renderButtonBack = (props: IDateRangeButtonProvideProps) => (
176
+ <ButtonIcon aria-label="Предыдущий период" {...props} />
177
+ );
178
+ const renderButtonForward = (props: IDateRangeButtonProvideProps) => (
179
+ <ButtonIcon aria-label="Следующий период" {...props} />
180
+ );
181
+
182
+ return (
183
+ <div style={{ maxWidth: "400px" }}>
184
+ <DateRange
185
+ value={value}
186
+ onChange={setValue}
187
+ shiftAmount={1}
188
+ shiftUnit={EDateRangeShiftUnit.MONTH}
189
+ renderPickerFrom={renderPicker}
190
+ renderPickerTo={renderPicker}
191
+ renderButtonBack={renderButtonBack}
192
+ renderButtonForward={renderButtonForward}
193
+ />
194
+ </div>
195
+ );
196
+ };
197
+ ```
198
+
199
+ ### Sizes
200
+
201
+ `stories/DateRange/examples/SizesExample.tsx`
202
+
203
+ ```tsx
204
+ import React, { useState } from "react";
205
+ import {
206
+ ButtonIcon,
207
+ DateField,
208
+ DateRange,
209
+ EDateRangeShiftUnit,
210
+ IDateRangeButtonProvideProps,
211
+ IDateRangePickerProvideProps,
212
+ TDateRangeValue,
213
+ FormFieldMaskedInput,
214
+ EComponentSize,
215
+ } from "@sberbusiness/triplex-next";
216
+
217
+ export const SizesExample = () => {
218
+ const [valueSM, setValueSM] = useState<TDateRangeValue>(["", ""]);
219
+ const [valueMD, setValueMD] = useState<TDateRangeValue>(["", ""]);
220
+ const [valueLG, setValueLG] = useState<TDateRangeValue>(["", ""]);
221
+
222
+ const renderPickerSM = (props: IDateRangePickerProvideProps) => (
223
+ <DateField
224
+ label="Label"
225
+ placeholderMask={FormFieldMaskedInput.presets.placeholderMasks.date}
226
+ invalidDateHint="Указана недоступная для выбора дата."
227
+ size={EComponentSize.SM}
228
+ {...props}
229
+ />
230
+ );
231
+ const renderPickerMD = (props: IDateRangePickerProvideProps) => (
232
+ <DateField
233
+ label="Label"
234
+ placeholderMask={FormFieldMaskedInput.presets.placeholderMasks.date}
235
+ invalidDateHint="Указана недоступная для выбора дата."
236
+ size={EComponentSize.MD}
237
+ {...props}
238
+ />
239
+ );
240
+
241
+ const renderPickerLG = (props: IDateRangePickerProvideProps) => (
242
+ <DateField
243
+ label="Label"
244
+ placeholderMask={FormFieldMaskedInput.presets.placeholderMasks.date}
245
+ invalidDateHint="Указана недоступная для выбора дата."
246
+ size={EComponentSize.LG}
247
+ {...props}
248
+ />
249
+ );
250
+ const renderButtonBack = (props: IDateRangeButtonProvideProps) => (
251
+ <ButtonIcon aria-label="Предыдущий период" {...props} />
252
+ );
253
+ const renderButtonForward = (props: IDateRangeButtonProvideProps) => (
254
+ <ButtonIcon aria-label="Следующий период" {...props} />
255
+ );
256
+
257
+ return (
258
+ <div style={{ display: "flex", gap: 24, flexDirection: "column" }}>
259
+ <div>
260
+ <div style={{ marginBottom: "8px", fontSize: "16px", fontWeight: "700" }}>SM</div>
261
+ <DateRange
262
+ value={valueSM}
263
+ onChange={setValueSM}
264
+ shiftAmount={1}
265
+ shiftUnit={EDateRangeShiftUnit.MONTH}
266
+ renderPickerFrom={renderPickerSM}
267
+ renderPickerTo={renderPickerSM}
268
+ renderButtonBack={renderButtonBack}
269
+ renderButtonForward={renderButtonForward}
270
+ />
271
+ </div>
272
+
273
+ <div>
274
+ <div style={{ marginBottom: "8px", fontSize: "16px", fontWeight: "700" }}>MD</div>
275
+ <DateRange
276
+ value={valueMD}
277
+ onChange={setValueMD}
278
+ shiftAmount={1}
279
+ shiftUnit={EDateRangeShiftUnit.MONTH}
280
+ renderPickerFrom={renderPickerMD}
281
+ renderPickerTo={renderPickerMD}
282
+ renderButtonBack={renderButtonBack}
283
+ renderButtonForward={renderButtonForward}
284
+ />
285
+ </div>
286
+
287
+ <div>
288
+ <div style={{ marginBottom: "8px", fontSize: "16px", fontWeight: "700" }}>LG</div>
289
+ <DateRange
290
+ value={valueLG}
291
+ onChange={setValueLG}
292
+ shiftAmount={1}
293
+ shiftUnit={EDateRangeShiftUnit.MONTH}
294
+ renderPickerFrom={renderPickerLG}
295
+ renderPickerTo={renderPickerLG}
296
+ renderButtonBack={renderButtonBack}
297
+ renderButtonForward={renderButtonForward}
298
+ />
299
+ </div>
300
+ </div>
301
+ );
302
+ };
303
+ ```
304
+
305
+ ### WithoutNavigation
306
+
307
+ `stories/DateRange/examples/WithoutNavigationExample.tsx`
308
+
309
+ ```tsx
310
+ import React, { useState } from "react";
311
+ import {
312
+ DateField,
313
+ DateRange,
314
+ IDateRangePickerProvideProps,
315
+ TDateRangeValue,
316
+ FormFieldMaskedInput,
317
+ } from "@sberbusiness/triplex-next";
318
+
319
+ export const WithoutNavigationExample = () => {
320
+ const [value, setValue] = useState<TDateRangeValue>(["", ""]);
321
+ const renderPicker = (props: IDateRangePickerProvideProps) => (
322
+ <DateField
323
+ label="Label"
324
+ placeholderMask={FormFieldMaskedInput.presets.placeholderMasks.date}
325
+ invalidDateHint="Указана недоступная для выбора дата."
326
+ {...props}
327
+ />
328
+ );
329
+
330
+ return (
331
+ <div style={{ maxWidth: "300px" }}>
332
+ <DateRange
333
+ value={value}
334
+ onChange={setValue}
335
+ hideNavigation
336
+ renderPickerFrom={renderPicker}
337
+ renderPickerTo={renderPicker}
338
+ renderButtonBack={() => null}
339
+ renderButtonForward={() => null}
340
+ />
341
+ </div>
342
+ );
343
+ };
344
+ ```
345
+
346
+ ### Example
347
+
348
+ `stories/DateRange/examples/ProductionExample.tsx`
349
+
350
+ ```tsx
351
+ import React, { useState } from "react";
352
+ import {
353
+ ButtonIcon,
354
+ DateField,
355
+ DateRange,
356
+ EDateRangeShiftUnit,
357
+ IDateRangeButtonProvideProps,
358
+ IDateRangePickerProvideProps,
359
+ TDateRangeValue,
360
+ FormFieldMaskedInput,
361
+ Text,
362
+ ETextSize,
363
+ EFontType,
364
+ Link,
365
+ } from "@sberbusiness/triplex-next";
366
+
367
+ export const ProductionExample = () => {
368
+ const [value, setValue] = useState<TDateRangeValue>(["", ""]);
369
+
370
+ const handleLinkClick: React.MouseEventHandler<HTMLAnchorElement> = (event) => {
371
+ event.preventDefault();
372
+ };
373
+
374
+ const renderPicker = (props: IDateRangePickerProvideProps) => (
375
+ <DateField
376
+ label="Label"
377
+ placeholderMask={FormFieldMaskedInput.presets.placeholderMasks.date}
378
+ invalidDateHint="Указана недоступная для выбора дата."
379
+ targetProps={{
380
+ description: (
381
+ <Text size={ETextSize.B4} type={EFontType.SECONDARY}>
382
+ (21) Description{" "}
383
+ <Link href="#" onClick={handleLinkClick}>
384
+ Link text
385
+ </Link>
386
+ </Text>
387
+ ),
388
+ }}
389
+ {...props}
390
+ />
391
+ );
392
+
393
+ const renderButtonBack = (props: IDateRangeButtonProvideProps) => (
394
+ <ButtonIcon aria-label="Предыдущий период" {...props} />
395
+ );
396
+ const renderButtonForward = (props: IDateRangeButtonProvideProps) => (
397
+ <ButtonIcon aria-label="Следующий период" {...props} />
398
+ );
399
+
400
+ return (
401
+ <div style={{ maxWidth: "400px" }}>
402
+ <DateRange
403
+ value={value}
404
+ onChange={setValue}
405
+ shiftAmount={1}
406
+ shiftUnit={EDateRangeShiftUnit.MONTH}
407
+ renderPickerFrom={renderPicker}
408
+ renderPickerTo={renderPicker}
409
+ renderButtonBack={renderButtonBack}
410
+ renderButtonForward={renderButtonForward}
411
+ />
412
+ </div>
413
+ );
414
+ };
415
+ ```
416
+
@@ -115,6 +115,7 @@ version: "1.0"
115
115
 
116
116
  | Дата | Изменение |
117
117
  |---|---|
118
+ | 2026-09-16 | Добавлен отступ `padding-bottom: 8px` для футера с `type={EFooterPageType.SECOND}`. |
118
119
  | 2026-08-06 | Sticky-позиционирование доступно вне `LightBox` при ширине viewport от 992px и отключено при высоте viewport не больше 800px. |
119
120
  | 2026-06-19 | Создан документ. AI-рефакторинг (clsx order, JSDoc на props, displayName), unit-тесты, миграция stories на modern pattern. |
120
121