@sberbusiness/triplex-next-mcp-server 1.45.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.
- package/README.md +7 -7
- package/bundle/components/AmountField.md +222 -323
- package/bundle/components/Avatar.md +138 -146
- package/bundle/components/CardAction.md +615 -0
- package/bundle/components/CardStatic.md +402 -0
- package/bundle/components/CheckboxTree.md +157 -215
- package/bundle/components/CheckboxTreeExtended.md +7 -7
- package/bundle/components/Chip.md +7 -6
- package/bundle/components/ChipGroup.md +1 -1
- package/bundle/components/ChipMonthYearPicker.md +344 -0
- package/bundle/components/ChipMultiselect.md +1251 -0
- package/bundle/components/ChipOptions.md +329 -0
- package/bundle/components/ChipSort.md +333 -0
- package/bundle/components/ChipSuggest.md +588 -0
- package/bundle/components/CollapsibleTree.md +8 -4
- package/bundle/components/CollapsibleTreeExtended.md +8 -6
- package/bundle/components/Confirm.md +346 -281
- package/bundle/components/DateField.md +222 -303
- package/bundle/components/DatePickerExtended.md +3 -1
- package/bundle/components/DateRange.md +351 -210
- package/bundle/components/FooterPage.md +1 -0
- package/bundle/components/FormField.md +14 -5
- package/bundle/components/FormGroup.md +3 -2
- package/bundle/components/HelpBox.md +7 -1
- package/bundle/components/IslandAccordion.md +443 -152
- package/bundle/components/IslandWidget.md +622 -157
- package/bundle/components/LightBox.md +8 -0
- package/bundle/components/ListMaster.md +2 -2
- package/bundle/components/ListTableItem.md +6 -6
- package/bundle/components/MaskedField.md +2 -1
- package/bundle/components/MonthYearField.md +18 -5
- package/bundle/components/Slider.md +389 -97
- package/bundle/components/SliderRange.md +486 -105
- package/bundle/components/StatusTracker.md +386 -94
- package/bundle/components/Stepper.md +355 -102
- package/bundle/components/StepperExtended.md +633 -0
- package/bundle/components/SuggestField.md +1049 -96
- package/bundle/components/SwipeableArea.md +7 -7
- package/bundle/components/Tabs.md +265 -92
- package/bundle/components/TabsExtended.md +18 -18
- package/bundle/components/TabsLine.md +301 -127
- package/bundle/components/TagColor.md +187 -66
- package/bundle/components/TagGroup.md +184 -48
- package/bundle/components/TextareaField.md +278 -70
- package/bundle/components/TopOverlay.md +17 -1
- package/bundle/components/TreeView.md +16 -7
- package/bundle/components/TriggerClickOnKeyDownEvent.md +277 -0
- package/bundle/components/UploadZone.md +318 -0
- package/bundle/guides/codestyle.md +16 -1
- package/bundle/guides/commits.md +47 -1
- package/bundle/guides/tests.md +23 -2
- package/bundle/manifest.json +92 -24
- package/bundle/release-notes/1.46.0.mdx +59 -0
- package/bundle/release-notes/1.47.0.mdx +122 -0
- package/bundle/release-notes/1.48.0.mdx +68 -0
- package/bundle/release-notes/1.49.0.mdx +7 -0
- package/bundle/tokens.json +3 -3
- package/dist/index.js +220 -6
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
component: DateRange
|
|
3
|
-
category:
|
|
4
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
26
|
+
### Обязательные props
|
|
96
27
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
-
|
|
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
|
-
|
|
164
|
-
`moment.subtract(1, 'month') = 2025-12-31` (moment сам обрабатывает
|
|
165
|
-
конец месяца). Для шага `1 year + 29 feb` — может быть скачок на 1
|
|
166
|
-
день. Это поведение `moment` и осознанное допущение.
|
|
45
|
+
### Формат значения
|
|
167
46
|
|
|
168
|
-
|
|
47
|
+
`TDateRangeValue` — `[start, end]`, обе даты строками в формате `YYYYMMDD` (`dateFormatYYYYMMDD` из `src/consts/DateConst`). Пустая строка означает «дата не выбрана»; допустимо состояние, когда заполнена только одна граница.
|
|
169
48
|
|
|
170
|
-
|
|
171
|
-
чтобы не сжималась).
|
|
49
|
+
### Что получают render-props
|
|
172
50
|
|
|
173
|
-
|
|
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
|
-
|
|
176
|
-
.dateRangeButton { z-index: 1; }
|
|
177
|
-
```
|
|
60
|
+
Объекты сформированы так, чтобы их можно было целиком спредить в `DateField` (пикеры) и `ButtonIcon` (кнопки) — именно так устроены все примеры в stories.
|
|
178
61
|
|
|
179
|
-
|
|
180
|
-
полями пикеров (Dropdown может показываться поверх). z-index: 1 даёт
|
|
181
|
-
кнопкам приоритет над содержимым пикера при совпадении контекста.
|
|
182
|
-
**Не удалять** — без этого кнопки могут "уйти под" пикер.
|
|
62
|
+
### Что уходит на корневой элемент
|
|
183
63
|
|
|
184
|
-
|
|
64
|
+
`className` объединяется с внутренним классом; остальные HTML-атрибуты (`id`, `style`, `data-*`, `aria-*`, обработчики) спредятся на корневой `div`. `children` компонент принимает по типу (от `HTMLAttributes`), но игнорирует: содержимое корневого `div` полностью определяется render-props.
|
|
185
65
|
|
|
186
|
-
|
|
187
|
-
Логично — не можем сдвинуть "не определённый" диапазон.
|
|
66
|
+
### Согласование границ
|
|
188
67
|
|
|
189
|
-
|
|
68
|
+
- Новая дата «от» больше текущей «до» → `onChange([date, ""])`: дата «до» сбрасывается.
|
|
69
|
+
- Новая дата «до» меньше текущей «от» → `onChange(["", date])`: дата «от» сбрасывается.
|
|
70
|
+
- Если новая дата пустая или противоположная граница пустая, проверка порядка не выполняется — значение записывается как есть.
|
|
71
|
+
- Сравнение строковое, без `moment`: формат `YYYYMMDD` лексикографически совпадает с хронологическим порядком. Поэтому формат дат менять нельзя (см. «Инварианты»).
|
|
190
72
|
|
|
191
|
-
|
|
192
|
-
только через UI-кнопки. Если `hideNavigation=true`, сдвиги недоступны.
|
|
73
|
+
### Сдвиг диапазона
|
|
193
74
|
|
|
194
|
-
|
|
195
|
-
|
|
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
|
-
|
|
79
|
+
---
|
|
199
80
|
|
|
200
|
-
|
|
201
|
-
оборачивать в собственный div с ref (deviates от паттерна).
|
|
81
|
+
## Дизайн-токены
|
|
202
82
|
|
|
203
|
-
|
|
83
|
+
Собственных дизайн-токенов у компонента нет — LESS-модуль описывает только раскладку (`display: flex`, отступы разделителя и кнопок, `z-index` кнопки). Внешний вид задают компоненты, которые потребитель вернул из render-props, и цвета иконок (`paletteIndex={5}`).
|
|
204
84
|
|
|
205
|
-
|
|
206
|
-
библиотеки. Возможно debt — при ревизии может быть добавлен.
|
|
85
|
+
Вертикальный отступ разделителя и кнопок подбирается под размер поля селекторами `:has([class*="sm"])`, `:has([class*="md"])`, `:has([class*="lg"])` — по подстроке в классе вложенного поля, а не по prop компонента.
|
|
207
86
|
|
|
208
87
|
---
|
|
209
88
|
|
|
210
89
|
## Инварианты
|
|
211
90
|
|
|
212
|
-
- **`
|
|
213
|
-
|
|
214
|
-
-
|
|
215
|
-
|
|
216
|
-
-
|
|
217
|
-
|
|
218
|
-
-
|
|
219
|
-
|
|
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
|
-
-
|
|
233
|
-
|
|
234
|
-
-
|
|
235
|
-
|
|
236
|
-
-
|
|
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
|
-
- `
|
|
252
|
-
- `
|
|
253
|
-
- `
|
|
254
|
-
|
|
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
|
-
|
|
124
|
+
Основные истории: `stories/DateRange/DateRange.stories.tsx`
|
|
125
|
+
Файлы примеров: `stories/DateRange/examples/`
|
|
262
126
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
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-
|
|
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
|
|