@sberbusiness/triplex-next-mcp-server 1.50.0-beta → 1.53.1

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 (74) hide show
  1. package/README.md +1 -0
  2. package/bundle/components/AlertProcess.md +6 -3
  3. package/bundle/components/AlertProcessSpoiler.md +216 -0
  4. package/bundle/components/Badge.md +4 -3
  5. package/bundle/components/ButtonDropdown.md +5 -3
  6. package/bundle/components/Carousel.md +671 -0
  7. package/bundle/components/CarouselExtended.md +7 -5
  8. package/bundle/components/CarouselIndicators.md +666 -0
  9. package/bundle/components/CarouselItem.md +579 -0
  10. package/bundle/components/CarouselNextButton.md +633 -0
  11. package/bundle/components/CarouselPrevButton.md +633 -0
  12. package/bundle/components/CarouselViewport.md +631 -0
  13. package/bundle/components/CheckboxTreeExtended.md +21 -7
  14. package/bundle/components/CheckboxTreeExtendedCheckbox.md +591 -0
  15. package/bundle/components/CheckboxTreeExtendedNode.md +577 -0
  16. package/bundle/components/Chip.md +1 -0
  17. package/bundle/components/ChipDatePicker.md +332 -0
  18. package/bundle/components/ChipDatePickerTarget.md +316 -0
  19. package/bundle/components/ChipMonthYearPicker.md +4 -3
  20. package/bundle/components/ChipMonthYearPickerTarget.md +318 -0
  21. package/bundle/components/ChipMultiselect.md +63 -73
  22. package/bundle/components/ChipSelect.md +385 -0
  23. package/bundle/components/ChipSelectTarget.md +388 -0
  24. package/bundle/components/ChipSuggest.md +10 -16
  25. package/bundle/components/ChipSuggestTarget.md +506 -0
  26. package/bundle/components/CollapsibleTree.md +2 -1
  27. package/bundle/components/CollapsibleTreeExtended.md +2 -1
  28. package/bundle/components/DateField.md +4 -2
  29. package/bundle/components/DatePickerExtended.md +16 -14
  30. package/bundle/components/DateRange.md +17 -10
  31. package/bundle/components/Dropdown.md +36 -63
  32. package/bundle/components/EmptyView.md +9 -4
  33. package/bundle/components/ExpandAnimation.md +9 -5
  34. package/bundle/components/FormField.md +1 -1
  35. package/bundle/components/ImageGallery.md +19 -52
  36. package/bundle/components/ImageGalleryExtended.md +123 -139
  37. package/bundle/components/ListItemControlsButtonDropdown.md +3 -2
  38. package/bundle/components/LoaderSmall.md +5 -4
  39. package/bundle/components/MonthYearRange.md +334 -0
  40. package/bundle/components/MultiselectField.md +81 -143
  41. package/bundle/components/Notification.md +2 -1
  42. package/bundle/components/NotificationGrouped.md +174 -0
  43. package/bundle/components/NumberField.md +259 -116
  44. package/bundle/components/OrderedList.md +149 -112
  45. package/bundle/components/Overlay.md +3 -2
  46. package/bundle/components/PageIndicators.md +306 -0
  47. package/bundle/components/Pagination.md +81 -3
  48. package/bundle/components/PaginationExtended.md +160 -0
  49. package/bundle/components/Portal.md +5 -2
  50. package/bundle/components/Puller.md +375 -0
  51. package/bundle/components/SMSField.md +277 -102
  52. package/bundle/components/SelectExtendedField.md +11 -7
  53. package/bundle/components/SelectField.md +7 -4
  54. package/bundle/components/SmartSpot.md +259 -0
  55. package/bundle/components/Step.md +2 -1
  56. package/bundle/components/Stepper.md +7 -7
  57. package/bundle/components/Suggest.md +7 -6
  58. package/bundle/components/SuggestField.md +8 -6
  59. package/bundle/components/TableBasic.md +12 -14
  60. package/bundle/components/Tooltip.md +9 -8
  61. package/bundle/guides/commits.md +4 -1
  62. package/bundle/guides/context.md +5 -5
  63. package/bundle/guides/stories.md +29 -21
  64. package/bundle/manifest.json +145 -13
  65. package/bundle/release-notes/1.50.0.mdx +4 -0
  66. package/bundle/release-notes/1.51.0.mdx +57 -0
  67. package/bundle/release-notes/1.52.0.mdx +55 -0
  68. package/bundle/release-notes/1.53.0.mdx +60 -0
  69. package/bundle/release-notes/1.54.0.mdx +45 -0
  70. package/bundle/release-notes/1.55.0.mdx +7 -0
  71. package/bundle/tokens.json +377 -222
  72. package/dist/index.js +39 -1
  73. package/dist/index.js.map +1 -1
  74. package/package.json +1 -1
@@ -1,9 +1,7 @@
1
1
  ---
2
2
  component: OrderedList
3
- category: Typography
4
- figma-node: ""
5
- figma-file: ""
6
- related: [UnorderedList, Typography, Text]
3
+ category: OrderedList
4
+ related: [UnorderedListExtended, UnorderedList]
7
5
  tokens: []
8
6
  stories: stories/OrderedList/OrderedList.stories.tsx
9
7
  version: "1.0"
@@ -13,157 +11,196 @@ version: "1.0"
13
11
 
14
12
  ## Назначение
15
13
 
16
- Семантический нумерованный список — `<ol>` с собственной нумерацией через
17
- CSS counter (а не нативные list-style-маркеры). Это даёт контроль над
18
- типографикой нумерации и позиционированием. Элементы — `<li>` с
19
- типографикой `Text size=B3`. Поддерживает `start` (с какой цифры начать).
14
+ Нумерованный типографический список с составным JSX-API: корневой `OrderedList`
15
+ рендерит `<ol>`, а `OrderedList.Item` — `<li>` с текстом и номером.
16
+ Номера рисует CSS-счётчик, начальное значение задаётся через `start`.
20
17
 
21
- Используй когда: нужен пронумерованный список с типографикой проекта.
22
- Для маркированного — `UnorderedList`.
18
+ Используй когда: порядок пунктов важен, а содержимое списка удобно собирать JSX-ом.
23
19
 
24
- ---
20
+ Не используй когда:
25
21
 
26
- ## Файловая структура
27
-
28
- ```text
29
- src/components/OrderedList/
30
- ├── OrderedList.tsx # forwardRef<HTMLOListElement> + Object.assign({Item})
31
- ├── OrderedListItem.tsx # forwardRef<HTMLLIElement>
32
- ├── types.ts # IOrderedListProps, IOrderedListItemProps
33
- ├── index.ts # Barrel
34
- ├── styles/
35
- │ ├── OrderedList.module.less # counter-reset
36
- │ └── OrderedListItem.module.less # counter-increment + ::before
37
- └── __tests__/
38
- ```
39
-
40
- ---
41
-
42
- ## Compound-структура
43
-
44
- ```tsx
45
- <OrderedList start={5}>
46
- <OrderedList.Item>Пятый</OrderedList.Item>
47
- <OrderedList.Item>Шестой</OrderedList.Item>
48
- </OrderedList>
49
- ```
50
-
51
- `OrderedList.Item` — единственный валидный child.
22
+ - Нужны маркеры вместо номеров — используй `UnorderedListExtended` для JSX-композиции
23
+ или `UnorderedList` для массива данных.
24
+ - Нужны строки с действиями, выделением или сортировкой — используй семейство `List`.
52
25
 
53
26
  ---
54
27
 
55
28
  ## Варианты и props
56
29
 
57
- ### `IOrderedListProps extends React.OlHTMLAttributes<HTMLOListElement>`
58
-
59
- | Prop | Тип | Default | Описание |
60
- |---|---|---|---|
61
- | `start` | `number` | — | С какой цифры начать (как у нативного `<ol>`). Влияет на CSS-counter (`--start-index-tx = start - 1`). |
62
- | `...OlHTMLAttributes` | — | — | Все атрибуты `<ol>`. |
30
+ ### Обязательные props
63
31
 
64
- ### `IOrderedListItemProps extends React.LiHTMLAttributes<HTMLLIElement>`
32
+ Обязательных props нет: `<OrderedList />` рендерит пустой `<ol>`.
65
33
 
66
- | Prop | Тип | Описание |
67
- |---|---|---|
68
- | `...LiHTMLAttributes` | — | Все атрибуты `<li>`. |
34
+ ### Опциональные props
69
35
 
70
- Внутри: `<Text tag="li" size=B3>` — типографика B3 фиксирована.
36
+ | Prop | Тип | По умолчанию | Описание |
37
+ |---|---|---|---|
38
+ | `children` | `React.ReactNode` | — | Пункты списка, обычно `OrderedList.Item`. |
39
+ | `start` | `number` | 1 | Начальный номер. Для нумерации передавай целое число; поддерживаются ноль и отрицательные значения. |
40
+ | `className` | `string` | — | Дополнительный класс корневого `<ol>`. |
41
+ | `style` | `React.CSSProperties` | — | Пользовательские стили корневого `<ol>`. |
42
+
43
+ `IOrderedListProps` наследует стандартные атрибуты `<ol>`, включая `aria-*`,
44
+ `data-*` и обработчики событий. Отдельных тем и размеров у списка нет.
45
+
46
+ ### Нумерация и объединение стилей
47
+
48
+ - При заданном `start` компонент передаёт его в HTML-атрибут и устанавливает
49
+ runtime-переменную `--triplex-next-runtime-OrderedList-Root_CounterReset` в `start - 1`.
50
+ Каждый `OrderedList.Item` увеличивает счётчик перед отображением номера.
51
+ - Остальные свойства `style` сохраняются, runtime-переменная записывается поверх них.
52
+ - Без `start` объект `style` передаётся без дополнений, а счётчик берёт значение 0
53
+ из класса самого списка — поэтому вложенный список не наследует сброс родителя.
54
+ После удаления `start` при повторном рендере inline-переменная удаляется.
55
+ - Номер добавляется стилями `OrderedList.Item`; произвольный `<li>` без этих стилей
56
+ не получает нарисованный компонентом номер.
57
+ - Стандартные `reversed`, `type` и `value` у пункта пробрасываются в DOM, но не
58
+ управляют собственным CSS-счётчиком. Обратная, буквенная нумерация и отдельный
59
+ номер пункта этим компонентом не реализованы.
71
60
 
72
61
  ---
73
62
 
74
- ## Ключевые особенности реализации
75
-
76
- ### Нумерация — через CSS counter, не нативный list-style
63
+ ## Дизайн-токены
77
64
 
78
- ```less
79
- .orderedList {
80
- --start-index-tx: 0;
81
- list-style: none;
82
- counter-reset: list-item-tx var(--start-index-tx);
83
- }
84
- .orderedListItem {
85
- counter-increment: list-item-tx;
86
- &::before { content: counter(list-item-tx) "."; ... }
87
- }
88
- ```
65
+ Собственных дизайн-токенов нет (`tokens: []`): стили списка задают отступы и
66
+ CSS-счётчик. `--triplex-next-runtime-OrderedList-Root_CounterReset` — runtime-переменная
67
+ нумерации по паттерну `docs/ai/codestyle.md`: не дизайн-токен и не публичный API,
68
+ через `ThemeProvider` не переопределяется.
69
+ Текст пункта оформляет `Text` с `ETextSize.B3`; его темизация описана в
70
+ [`Text-ai.md`](../Typography/Text-ai.md).
89
71
 
90
- - **`list-style: none`** + `::before` с `counter()` — позволяет полностью контролировать визуал нумерации (font, цвет, позиционирование).
91
- - **`+ .orderedListItem { margin-top: 8px }`** — gap между соседними items.
92
- - **`position: absolute; left: 0`** на `::before` — нумерация ровняется по левому краю; `padding-left: 25px` на `<ol>` оставляет место.
72
+ ---
93
73
 
94
- ### `start` через CSS-переменную
74
+ ## Инварианты
95
75
 
96
- ```tsx
97
- style={start !== undefined ? { ...style, "--start-index-tx": start - 1 } : style}
98
- ```
76
+ - Сохраняй `forwardRef`: ref списка указывает на корневой `<ol>`.
77
+ - Сохраняй составной API `OrderedList.Item` и отдельный экспорт `OrderedListItem`.
78
+ Пункт рендерится как `<li>`, его ref указывает на этот элемент.
79
+ - `IOrderedListProps` и `IOrderedListItemProps` остаются публичными интерфейсами
80
+ стандартных HTML-атрибутов; barrel-экспорты не удаляются.
81
+ - Не заменяй проверку `start !== undefined` на проверку truthy: `start={0}` допустим.
82
+ - Сохраняй связь `start - 1` с CSS-счётчиком пунктов и порядок `{...style, "--...": start - 1}`.
83
+ Имя runtime-переменной меняй разом в `OrderedList.tsx` и `OrderedList.module.less`:
84
+ оно видно в inline-стиле корневого `<ol>`.
85
+ - Не убирай объявление runtime-переменной со значением 0 в классе `.orderedList`:
86
+ без него вложенный список унаследует сброс счётчика родителя.
87
+ - `className` объединяется через `clsx`, остальные атрибуты пробрасываются на `<ol>`.
88
+ `data-tx` устанавливается из версии пакета после spread пользовательских атрибутов.
89
+ - `displayName` списка — `"OrderedList"`, пункта — `"OrderedListItem"`.
90
+ - Story exports `Default`, `WithLongContent` и `Start` определяют IDs визуальных тестов;
91
+ `Playground` из скриншот-тестов исключён.
99
92
 
100
- - React inline-style с CSS-переменной (`as keyof React.CSSProperties` cast — TS-хак).
101
- - `start - 1` потому что `counter-increment` срабатывает **до** рендера `::before`, поэтому нужно начать на 1 меньше.
102
- - Также пробрасывается **нативный `start` атрибут** на `<ol>` — для семантики (скринридеры, copy-paste).
93
+ ---
103
94
 
104
- ### `OrderedListItem` — типография зашита
95
+ ## Accessibility
105
96
 
106
- ```tsx
107
- <Text tag="li" size={ETextSize.B3}>
108
- ```
97
+ Используется семантическая разметка `<ol>` / `<li>`. Компонент не задаёт собственных
98
+ ARIA-атрибутов, не управляет фокусом и не обрабатывает клавиатуру. При необходимости
99
+ потребитель передаёт `aria-label` или `aria-labelledby` на список. Номер пункта
100
+ рисуется через `::before`, а текст приходит через `children`.
109
101
 
110
- - Размер **`B3`** — изменение требует копирования компонента или создания новой `size`-prop'ы.
102
+ ---
111
103
 
112
- ### `Object.assign` — `OrderedList.Item`
104
+ ## Связанные компоненты
113
105
 
114
- Стандартный pattern для compound.
106
+ - `OrderedListItem` / `OrderedList.Item` — публичная обёртка над `Text` с `tag="li"`
107
+ и `ETextSize.B3`, добавляющая стили номера и интервал между пунктами. Это тривиальная
108
+ обёртка без отдельного AI.md.
109
+ - `UnorderedListExtended` — альтернатива с составным JSX-API и произвольными маркерами.
110
+ - `UnorderedList` — альтернатива с маркированными пунктами, заданными массивом `items`.
111
+ - `Text` — типографика пункта списка; прямые props типографики не входят в
112
+ `IOrderedListItemProps`.
115
113
 
116
- ### `data-tx` — версия пакета
114
+ ---
117
115
 
118
- На обоих корнях (`<ol>` и `<li>`).
116
+ ## Stories
119
117
 
120
- ---
118
+ Основные истории: `stories/OrderedList/OrderedList.stories.tsx`
119
+ Файлы примеров: `stories/OrderedList/examples/`
121
120
 
122
- ## Инварианты
121
+ | Story | Example file | Что демонстрирует |
122
+ |---|---|---|
123
+ | `Playground` | `Playground.tsx` | Интерактивная настройка `start`. |
124
+ | `Default` | `Default.tsx` | Список из трёх пунктов с нумерацией от 1. |
125
+ | `WithLongContent` | `WithLongContent.tsx` | Перенос длинного текста пункта на вторую строку. |
126
+ | `Start` | `Start.tsx` | Начало нумерации с 5, 0 и −2. |
123
127
 
124
- - **`<ol>` с `list-style: none`** — нумерация рисуется через CSS-counter (`::before` с `counter()`).
125
- - **`OrderedListItem` — `<li>` через `<Text tag="li" size=B3>`** — типография B3 не настраивается.
126
- - **`start - 1`** в CSS-переменной (counter-increment-механика). При смене на `counter-reset` без `-1` — начнётся со start+1.
127
- - **Gap = 8px** между items (`+ .orderedListItem`).
128
- - **`start` дублируется** в DOM (`<ol start>`) и CSS-переменной — оба источника правды (DOM — для семантики, CSS — для рендера).
129
- - **ForwardRef** на обоих компонентах.
130
- - **Compound — `OrderedList.Item`** через `Object.assign`.
131
- - **`--start-index-tx`** — публичное имя CSS-переменной; не переименовывать.
128
+ `Default`, `WithLongContent` и `Start` участвуют в визуальных тестах на xs/xl. `Start` покрывает
129
+ нестандартное начало нумерации без отдельной дублирующей `VisualTests` story.
132
130
 
133
131
  ---
134
132
 
135
- ## Accessibility
133
+ ## История изменений
136
134
 
137
- - `<ol>` + `<li>` — нативная семантика списка → скринридеры читают «список из N элементов, элемент 1 из N».
138
- - `start` пробрасывается в DOM — скринридер читает корректные номера.
139
- - `list-style: none` отключает **нативные маркеры**, но семантика `<ol>` сохраняется (Safari/VoiceOver на старых версиях иногда теряют семантику с `list-style: none` — известный edge case).
140
- - `::before` content `"1."` — может быть прочитан в зависимости от reader (не на всех — спецификация неоднозначна).
135
+ | Дата | Изменение |
136
+ |---|---|
137
+ | 2026-10-07 | Создан документ. Уточнена типизация CSS-счётчика и JSDoc существующих props; CSS-переменная `--start-index-tx` переименована в `--triplex-next-runtime-OrderedList-Root_CounterReset`; добавлены тесты `start`, объединения `style` и повторного рендера, stories `Playground` и `Start`. |
141
138
 
142
139
  ---
143
140
 
144
- ## Дизайн-токены
141
+ ## Примеры использования
145
142
 
146
- Нет собственных. Типография — `B3` от `Text`. Размер padding-left = 25px, gap = 8px — захардкожены в LESS (не переменные).
143
+ ### Default
147
144
 
148
- ---
145
+ `stories/OrderedList/examples/Default.tsx`
149
146
 
150
- ## Связанные компоненты
147
+ ```tsx
148
+ import React from "react";
149
+ import { OrderedList } from "@sberbusiness/triplex-next";
150
+
151
+ export const Default = () => (
152
+ <div style={{ maxWidth: 200 }}>
153
+ <OrderedList>
154
+ <OrderedList.Item>List item text;</OrderedList.Item>
155
+ <OrderedList.Item>List item text;</OrderedList.Item>
156
+ <OrderedList.Item>List item text.</OrderedList.Item>
157
+ </OrderedList>
158
+ </div>
159
+ );
160
+ ```
151
161
 
152
- - `UnorderedList` — маркированный аналог.
153
- - `Text` (Typography) — типография элементов.
154
- - `ETextSize.B3` — размер.
162
+ ### WithLongContent
155
163
 
156
- ---
164
+ `stories/OrderedList/examples/WithLongContent.tsx`
157
165
 
158
- ## Stories
166
+ ```tsx
167
+ import React from "react";
168
+ import { OrderedList } from "@sberbusiness/triplex-next";
169
+
170
+ export const WithLongContent = () => (
171
+ <div style={{ maxWidth: 200 }}>
172
+ <OrderedList>
173
+ <OrderedList.Item>List item text;</OrderedList.Item>
174
+ <OrderedList.Item>List item text;</OrderedList.Item>
175
+ <OrderedList.Item>Extremely long list item that fits in two lines.</OrderedList.Item>
176
+ </OrderedList>
177
+ </div>
178
+ );
179
+ ```
159
180
 
160
- `stories/OrderedList/OrderedList.stories.tsx`
161
- Примеры: `stories/OrderedList/examples/`
181
+ ### Start
162
182
 
163
- ---
183
+ `stories/OrderedList/examples/Start.tsx`
164
184
 
165
- ## История изменений
185
+ ```tsx
186
+ import React from "react";
187
+ import { OrderedList } from "@sberbusiness/triplex-next";
188
+
189
+ const START_VALUES = [5, 0, -2];
190
+
191
+ export const Start = () => (
192
+ <div style={{ display: "flex", alignItems: "flex-start", gap: 24, flexWrap: "wrap" }}>
193
+ {START_VALUES.map((start) => (
194
+ <div key={start} style={{ width: 200, height: 112, flexShrink: 0 }}>
195
+ <div style={{ marginBottom: 8, fontSize: 16, fontWeight: 700 }}>start={start}</div>
196
+ <OrderedList start={start}>
197
+ <OrderedList.Item>List item text;</OrderedList.Item>
198
+ <OrderedList.Item>List item text;</OrderedList.Item>
199
+ <OrderedList.Item>List item text.</OrderedList.Item>
200
+ </OrderedList>
201
+ </div>
202
+ ))}
203
+ </div>
204
+ );
205
+ ```
166
206
 
167
- | Дата | Изменение |
168
- |---|---|
169
- | 2026-04-18 | Создан документ |
@@ -184,8 +184,8 @@ unit-тестами (`toHaveBeenCalledTimes(1)`).
184
184
  - `OverlayMask` (`Overlay.Mask`) — кликабельный фон.
185
185
  - `OverlayPanel` (`Overlay.Panel`) — выезжающая панель.
186
186
  - `LightBox`, `TopOverlay` — построены поверх механики оверлея.
187
- - `DropdownMobile` — мобильный оверлей `Dropdown`, построен на `OverlayBase`. Своего AI.md
188
- не имеет, описан в `Dropdown-ai.md`.
187
+ - `Puller` — нижняя шторка (мобильные версии `Dropdown`, `Tooltip` и др.), построена на
188
+ `OverlayBase`. Описан в `Puller-ai.md`.
189
189
  - `ModalWindow` — готовое центрированное модальное окно с шапкой, телом и футером, если
190
190
  нужен не низкоуровневый оверлей. На `Overlay` не построен — это отвод в другой класс
191
191
  задач, поэтому в `related` не входит.
@@ -217,6 +217,7 @@ Visual baseline для `components-overlay--visual-tests` генерируетс
217
217
  | 2026-08-27 | `OverlayBase` вызывает `onOpen` при монтировании с `opened={true}` — раньше первый рендер гасил все колбэки, и оверлей, смонтированный открытым, не сообщал об этом потребителю (release notes 1.44.0). |
218
218
  | 2026-06-17 | Создан документ. `Overlay` переведён на `forwardRef` (ref → корневой `div`, аддитивно, release notes 1.34.0). Дедуплицирован флаг `closing` (единый источник — `OverlayBase`). Codestyle/JSDoc cleanup в 4 файлах. Добавлены 4 unit-тест-файла. Исправлен двойной вызов `onClosing`/`onOpening` за переход — теперь ровно один раз (закреплено `toHaveBeenCalledTimes(1)`). |
219
219
  | 2026-09-29 | Аудит симметрии `related` (TRI-156): из `related` убраны имена без строки в `docs/ai/ROADMAP.md` (`OverlayBase`, `OverlayMask`, `OverlayPanel`, `DropdownMobile`); из `related` убраны несимметричные имена (`ModalWindow`). Пояснения к убранным именам сохранены прозой в «Связанные компоненты»; публичный API и поведение не затронуты. |
220
+ | 2026-10-21 | Переход на `Puller` (TRIPLEX-961): связанный `DropdownMobile` теперь `Puller`. |
220
221
 
221
222
  ---
222
223
 
@@ -0,0 +1,306 @@
1
+ ---
2
+ component: PageIndicators
3
+ category: Navigation
4
+ related: []
5
+ tokens:
6
+ - PageIndicators.Indicator_Inactive_BackgroundColor_Default
7
+ - PageIndicators.Indicator_Inactive_BackgroundColor_Hover
8
+ - PageIndicators.Indicator_Inactive_BackgroundColor_Pressed
9
+ - PageIndicators.Indicator_Inactive_BackgroundColor_Disabled
10
+ - PageIndicators.Indicator_Active_BackgroundColor_Default
11
+ - PageIndicators.Indicator_Active_BackgroundColor_Hover
12
+ - PageIndicators.Indicator_Active_BackgroundColor_Pressed
13
+ - PageIndicators.Indicator_Active_BackgroundColor_Disabled
14
+ - PageIndicators.Indicator_OutlineColor
15
+ stories: stories/PageIndicators/PageIndicators.stories.tsx
16
+ version: "1.0"
17
+ ---
18
+
19
+ # PageIndicators
20
+
21
+ ## Назначение
22
+
23
+ Controlled-ряд индикаторов страниц: показывает, какая страница из `count` активна,
24
+ и позволяет переключиться кликом или с клавиатуры. Одновременно видимо окно из 5
25
+ индикаторов: активный — длинный, остальные — короче, а крайние индикаторы окна
26
+ уменьшаются, если за ними есть ещё страницы. Смена страницы анимируется.
27
+
28
+ Используй когда: нужен компактный индикатор положения в наборе слайдов/страниц —
29
+ карусель, галерея изображений, онбординг-шаги без подписей.
30
+
31
+ Не используй когда: страниц много и пользователю нужен переход к конкретному
32
+ номеру или выбор размера страницы — для этого `Pagination`. Не используй как
33
+ индикатор прогресса процесса с подписями шагов — для этого `Stepper`.
34
+
35
+ ---
36
+
37
+ ## Варианты и props
38
+
39
+ Своего состояния активной страницы компонент не держит — `activeIndex` и
40
+ `onChange` всегда задаёт потребитель.
41
+
42
+ ### Обязательные props
43
+
44
+ | Prop | Тип | Описание |
45
+ |---|---|---|
46
+ | `count` | `number` | Количество страниц. При `count < 1` компонент ничего не рендерит |
47
+ | `activeIndex` | `number` | Индекс активной страницы (с 0). Значение вне диапазона приводится к ближайшей странице, чтобы у ряда всегда был активный индикатор, доступный по Tab |
48
+ | `onChange` | `(index: number) => void` | Выбор страницы кликом или с клавиатуры. С клавиатуры вызывается только при реальной смене индекса (на границе диапазона — нет) |
49
+
50
+ ### Опциональные props
51
+
52
+ | Prop | Тип | По умолчанию | Описание |
53
+ |---|---|---|---|
54
+ | `orientation` | `EOrientation` | `HORIZONTAL` | `HORIZONTAL` (`'horizontal'`) / `VERTICAL` (`'vertical'`): направление ряда и набор клавиш-стрелок |
55
+ | `indicatorProps` | `TPageIndicatorProps \| TPageIndicatorPropsFactory` | — | Свойства кнопок-индикаторов: объект для всех или функция от `{ index, page, selected }`. Через неё задают `aria-label`, `disabled`, `data-*` |
56
+ | `...HTMLDivAttributes` | — | — | Пробрасываются на корневой `<div>`. Кроме `onChange` (переопределён под выбор страницы) |
57
+
58
+ В `indicatorProps` компонент переопределяет `role`, `tabIndex`, `aria-selected`
59
+ (и `aria-hidden` для индикаторов вне окна); `className` и `style` мерджатся с
60
+ собственными. `onClick` из `indicatorProps` вызывается после `onChange`.
61
+
62
+ ---
63
+
64
+ ## Дизайн-токены
65
+
66
+ Переопределяются через `ThemeProvider` (prop `tokens`) — см. `ThemeProvider-ai.md` →
67
+ «Как переопределять токены». Значения по умолчанию — `src/components/DesignTokens/components/PageIndicators.ts`.
68
+
69
+ ```text
70
+ PageIndicators.Indicator_Inactive_BackgroundColor_Default
71
+ PageIndicators.Indicator_Inactive_BackgroundColor_Hover
72
+ PageIndicators.Indicator_Inactive_BackgroundColor_Pressed
73
+ PageIndicators.Indicator_Inactive_BackgroundColor_Disabled
74
+
75
+ PageIndicators.Indicator_Active_BackgroundColor_Default
76
+ PageIndicators.Indicator_Active_BackgroundColor_Hover
77
+ PageIndicators.Indicator_Active_BackgroundColor_Pressed
78
+ PageIndicators.Indicator_Active_BackgroundColor_Disabled
79
+
80
+ PageIndicators.Indicator_OutlineColor
81
+ ```
82
+
83
+ Runtime CSS-переменные (задаются компонентом через `style`, **не** дизайн-токены,
84
+ не попадают в `DesignTokens`):
85
+
86
+ ```
87
+ --triplex-next-runtime-PageIndicators-Root_Size
88
+ --triplex-next-runtime-PageIndicators-Indicator_Scale
89
+ --triplex-next-runtime-PageIndicators-Indicator_Translate
90
+ ```
91
+
92
+ ---
93
+
94
+ ## Инварианты
95
+
96
+ - **`forwardRef`** обязателен; target — корневой `<div role="tablist">`.
97
+ - **Controlled-only.** Не добавлять внутреннее состояние активной страницы —
98
+ `Carousel.Indicators` и `ImageGalleryExtended.PageIndicators` управляют им сами.
99
+ - **Геометрия окна** (5 видимых, размеры 24 / 16 / 8 px, зазор 8 px) — визуальный
100
+ контракт; изменение ломает скриншот-регрессию `Carousel` и галереи.
101
+ - **Кэшированные колбэки индикаторов** (ref и click по индексу) стабильны между
102
+ рендерами; актуальные `onChange` / `indicatorProps` читаются из рефов. Не
103
+ замыкать их напрямую — иначе устаревшее замыкание после ререндера.
104
+ - **Отступы** (`padding: 1px` + `margin: -1px`) оставляют место для focus outline
105
+ при `overflow: hidden`. Внешние отступы задаёт потребитель через `className`
106
+ (селектором большей специфичности).
107
+ - **Публичный API** (`PageIndicators`, общий `EOrientation` и значения
108
+ `'horizontal'`/`'vertical'`, `IPageIndicatorsProps`, `TPageIndicatorProps`,
109
+ `TPageIndicatorPropsFactory`, форма аргумента фабрики `{ index, page, selected }`)
110
+ — breaking change при изменении.
111
+
112
+ ---
113
+
114
+ ## Accessibility
115
+
116
+ - **Роли:** корень — `role="tablist"` с `aria-orientation`, индикаторы —
117
+ `<button role="tab">` с `aria-selected`.
118
+ - **Roving tabindex:** в последовательности Tab только активный индикатор
119
+ (`tabIndex=0`), остальные — `tabIndex=-1`. Если активный отключён, Tab-stop
120
+ получает ближайший доступный в видимом окне; если там доступных нет, Tab-stop
121
+ у ряда нет (скрытые индикаторы недоступны и мыши).
122
+ - **Клавиатура** на ряду: `→` / `←` (горизонтально) или `↓` / `↑` (вертикально) —
123
+ следующая/предыдущая страница без зацикливания; `Home` / `End` — первая/последняя.
124
+ Обработанные клавиши получают `preventDefault`; `onKeyDown` потребителя
125
+ вызывается всегда.
126
+ - **Фокус следует за выбором:** если фокус внутри ряда, после смены `activeIndex`
127
+ он переносится на новый активный индикатор (в `requestAnimationFrame`).
128
+ - **Индикаторы вне окна** помечены `aria-hidden="true"`.
129
+ - **Отключённые индикаторы** (`disabled` через `indicatorProps`) пропускаются
130
+ клавиатурой: стрелки, `Home` и `End` ведут к ближайшему доступному.
131
+ - **`aria-label` — обязанность потребителя:** компонент не хардкодит язык.
132
+ Подпись индикаторам задают через `indicatorProps` (например, `Страница N`), ряду —
133
+ `aria-label` на корне.
134
+
135
+ ---
136
+
137
+ ## Связанные компоненты
138
+
139
+ - `Carousel` — `Carousel.Indicators` рендерит `PageIndicators`, беря `count`,
140
+ `activeIndex`, `onChange` и ориентацию из контекста карусели; в режиме
141
+ `ECarouselScrollMode.ITEM` ничего не рендерит. Карусель добавляет только
142
+ раскладочные отступы и мобильное позиционирование.
143
+ - `ImageGalleryExtended` — часть `.PageIndicators` рендерит `PageIndicators` с
144
+ индикатором на каждое изображение; `aria-label` по умолчанию — `item.alt`.
145
+ - `Pagination` — альтернатива для длинных списков с номерами страниц.
146
+
147
+ ---
148
+
149
+ ## Stories
150
+
151
+ Основные истории: `stories/PageIndicators/PageIndicators.stories.tsx`
152
+ Файлы примеров: `stories/PageIndicators/examples/`
153
+
154
+ | Story | Example file | Что демонстрирует |
155
+ |---|---|---|
156
+ | `Playground` | `Playground.tsx` | Интерактивный контроль `count` и `orientation` |
157
+ | `Default` | `Default.tsx` | Базовый горизонтальный ряд из 5 страниц |
158
+ | `Orientations` | `Orientations.tsx` | Горизонтальная и вертикальная ориентация |
159
+ | `ManyPages` | `ManyPages.tsx` | 12 страниц: скользящее окно из 5 индикаторов |
160
+ | `CustomIndicatorProps` | `CustomIndicatorProps.tsx` | Фабрика `indicatorProps`: `aria-label`, `aria-controls`, `data-*` и связь с `tabpanel` |
161
+ | `VisualTests` | `VisualTests.tsx` | Скриншот-регрессия: фокус, окно в начале/середине/конце, disabled, вертикальная ориентация |
162
+
163
+ ---
164
+
165
+ ## История изменений
166
+
167
+ | Дата | Изменение |
168
+ |---|---|
169
+ | 2026-10-07 | Создан документ: компонент вынесен из `Carousel.Indicators` и встроен в `ImageGalleryExtended` |
170
+
171
+ ---
172
+
173
+ ## Примеры использования
174
+
175
+ ### Default
176
+
177
+ `stories/PageIndicators/examples/Default.tsx`
178
+
179
+ ```tsx
180
+ import React, { useState } from "react";
181
+ import { PageIndicators } from "@sberbusiness/triplex-next";
182
+
183
+ export const Default = () => {
184
+ const [activeIndex, setActiveIndex] = useState(0);
185
+
186
+ return (
187
+ <PageIndicators
188
+ count={5}
189
+ activeIndex={activeIndex}
190
+ onChange={setActiveIndex}
191
+ aria-label="Страницы"
192
+ indicatorProps={({ page }) => ({ "aria-label": `Страница ${page}` })}
193
+ />
194
+ );
195
+ };
196
+ ```
197
+
198
+ ### Orientations
199
+
200
+ `stories/PageIndicators/examples/Orientations.tsx`
201
+
202
+ ```tsx
203
+ import React, { useState } from "react";
204
+ import { PageIndicators, EOrientation } from "@sberbusiness/triplex-next";
205
+
206
+ interface IOrientationItemProps {
207
+ orientation: EOrientation;
208
+ }
209
+
210
+ const OrientationItem = ({ orientation }: IOrientationItemProps) => {
211
+ const [activeIndex, setActiveIndex] = useState(0);
212
+
213
+ return (
214
+ <div>
215
+ <div style={{ marginBottom: 8, fontSize: 16, fontWeight: 700 }}>{orientation.toUpperCase()}</div>
216
+ <PageIndicators
217
+ count={5}
218
+ activeIndex={activeIndex}
219
+ onChange={setActiveIndex}
220
+ orientation={orientation}
221
+ aria-label="Страницы"
222
+ indicatorProps={({ page }) => ({ "aria-label": `Страница ${page}` })}
223
+ />
224
+ </div>
225
+ );
226
+ };
227
+
228
+ const ORIENTATIONS = Object.values(EOrientation);
229
+
230
+ export const Orientations = () => (
231
+ <div style={{ display: "flex", alignItems: "flex-start", gap: 32, flexWrap: "wrap" }}>
232
+ {ORIENTATIONS.map((orientation) => (
233
+ <OrientationItem key={orientation} orientation={orientation} />
234
+ ))}
235
+ </div>
236
+ );
237
+ ```
238
+
239
+ ### ManyPages
240
+
241
+ `stories/PageIndicators/examples/ManyPages.tsx`
242
+
243
+ ```tsx
244
+ import React, { useState } from "react";
245
+ import { PageIndicators } from "@sberbusiness/triplex-next";
246
+
247
+ /**
248
+ * Одновременно видно окно из 5 индикаторов. Если за окном есть ещё страницы,
249
+ * крайние индикаторы окна уменьшаются; при переключении окно сдвигается за активным.
250
+ */
251
+ export const ManyPages = () => {
252
+ const [activeIndex, setActiveIndex] = useState(0);
253
+
254
+ return (
255
+ <PageIndicators
256
+ count={12}
257
+ activeIndex={activeIndex}
258
+ onChange={setActiveIndex}
259
+ aria-label="Страницы"
260
+ indicatorProps={({ page }) => ({ "aria-label": `Страница ${page}` })}
261
+ />
262
+ );
263
+ };
264
+ ```
265
+
266
+ ### CustomIndicatorProps
267
+
268
+ `stories/PageIndicators/examples/CustomIndicatorProps.tsx`
269
+
270
+ ```tsx
271
+ import React, { useState } from "react";
272
+ import { PageIndicators, TPageIndicatorPropsFactory } from "@sberbusiness/triplex-next";
273
+
274
+ const PAGES_COUNT = 5;
275
+
276
+ /** Свойства каждого индикатора вычисляются из его состояния: индекса, номера страницы и активности. */
277
+ const getIndicatorProps: TPageIndicatorPropsFactory = ({ index, page, selected }) => ({
278
+ id: `page-tab-${index}`,
279
+ "aria-label": `Страница ${page}`,
280
+ "aria-controls": "page-panel",
281
+ title: selected ? `Страница ${page} (текущая)` : `Страница ${page}`,
282
+ // data-атрибуты удобны для e2e-тестов и аналитики.
283
+ "data-page": page,
284
+ "data-selected": selected,
285
+ });
286
+
287
+ export const CustomIndicatorProps = () => {
288
+ const [activeIndex, setActiveIndex] = useState(0);
289
+
290
+ return (
291
+ <div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
292
+ <PageIndicators
293
+ count={PAGES_COUNT}
294
+ activeIndex={activeIndex}
295
+ onChange={setActiveIndex}
296
+ aria-label="Страницы"
297
+ indicatorProps={getIndicatorProps}
298
+ />
299
+ <div id="page-panel" role="tabpanel" aria-labelledby={`page-tab-${activeIndex}`}>
300
+ Содержимое страницы {activeIndex + 1} из {PAGES_COUNT}
301
+ </div>
302
+ </div>
303
+ );
304
+ };
305
+ ```
306
+