@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.
- package/README.md +1 -0
- package/bundle/components/AlertProcess.md +6 -3
- package/bundle/components/AlertProcessSpoiler.md +216 -0
- package/bundle/components/Badge.md +4 -3
- package/bundle/components/ButtonDropdown.md +5 -3
- package/bundle/components/Carousel.md +671 -0
- package/bundle/components/CarouselExtended.md +7 -5
- package/bundle/components/CarouselIndicators.md +666 -0
- package/bundle/components/CarouselItem.md +579 -0
- package/bundle/components/CarouselNextButton.md +633 -0
- package/bundle/components/CarouselPrevButton.md +633 -0
- package/bundle/components/CarouselViewport.md +631 -0
- package/bundle/components/CheckboxTreeExtended.md +21 -7
- package/bundle/components/CheckboxTreeExtendedCheckbox.md +591 -0
- package/bundle/components/CheckboxTreeExtendedNode.md +577 -0
- package/bundle/components/Chip.md +1 -0
- package/bundle/components/ChipDatePicker.md +332 -0
- package/bundle/components/ChipDatePickerTarget.md +316 -0
- package/bundle/components/ChipMonthYearPicker.md +4 -3
- package/bundle/components/ChipMonthYearPickerTarget.md +318 -0
- package/bundle/components/ChipMultiselect.md +63 -73
- package/bundle/components/ChipSelect.md +385 -0
- package/bundle/components/ChipSelectTarget.md +388 -0
- package/bundle/components/ChipSuggest.md +10 -16
- package/bundle/components/ChipSuggestTarget.md +506 -0
- package/bundle/components/CollapsibleTree.md +2 -1
- package/bundle/components/CollapsibleTreeExtended.md +2 -1
- package/bundle/components/DateField.md +4 -2
- package/bundle/components/DatePickerExtended.md +16 -14
- package/bundle/components/DateRange.md +17 -10
- package/bundle/components/Dropdown.md +36 -63
- package/bundle/components/EmptyView.md +9 -4
- package/bundle/components/ExpandAnimation.md +9 -5
- package/bundle/components/FormField.md +1 -1
- package/bundle/components/ImageGallery.md +19 -52
- package/bundle/components/ImageGalleryExtended.md +123 -139
- package/bundle/components/ListItemControlsButtonDropdown.md +3 -2
- package/bundle/components/LoaderSmall.md +5 -4
- package/bundle/components/MonthYearRange.md +334 -0
- package/bundle/components/MultiselectField.md +81 -143
- package/bundle/components/Notification.md +2 -1
- package/bundle/components/NotificationGrouped.md +174 -0
- package/bundle/components/NumberField.md +259 -116
- package/bundle/components/OrderedList.md +149 -112
- package/bundle/components/Overlay.md +3 -2
- package/bundle/components/PageIndicators.md +306 -0
- package/bundle/components/Pagination.md +81 -3
- package/bundle/components/PaginationExtended.md +160 -0
- package/bundle/components/Portal.md +5 -2
- package/bundle/components/Puller.md +375 -0
- package/bundle/components/SMSField.md +277 -102
- package/bundle/components/SelectExtendedField.md +11 -7
- package/bundle/components/SelectField.md +7 -4
- package/bundle/components/SmartSpot.md +259 -0
- package/bundle/components/Step.md +2 -1
- package/bundle/components/Stepper.md +7 -7
- package/bundle/components/Suggest.md +7 -6
- package/bundle/components/SuggestField.md +8 -6
- package/bundle/components/TableBasic.md +12 -14
- package/bundle/components/Tooltip.md +9 -8
- package/bundle/guides/commits.md +4 -1
- package/bundle/guides/context.md +5 -5
- package/bundle/guides/stories.md +29 -21
- package/bundle/manifest.json +145 -13
- package/bundle/release-notes/1.50.0.mdx +4 -0
- package/bundle/release-notes/1.51.0.mdx +57 -0
- package/bundle/release-notes/1.52.0.mdx +55 -0
- package/bundle/release-notes/1.53.0.mdx +60 -0
- package/bundle/release-notes/1.54.0.mdx +45 -0
- package/bundle/release-notes/1.55.0.mdx +7 -0
- package/bundle/tokens.json +377 -222
- package/dist/index.js +39 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
component: OrderedList
|
|
3
|
-
category:
|
|
4
|
-
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
58
|
-
|
|
59
|
-
| Prop | Тип | Default | Описание |
|
|
60
|
-
|---|---|---|---|
|
|
61
|
-
| `start` | `number` | — | С какой цифры начать (как у нативного `<ol>`). Влияет на CSS-counter (`--start-index-tx = start - 1`). |
|
|
62
|
-
| `...OlHTMLAttributes` | — | — | Все атрибуты `<ol>`. |
|
|
30
|
+
### Обязательные props
|
|
63
31
|
|
|
64
|
-
|
|
32
|
+
Обязательных props нет: `<OrderedList />` рендерит пустой `<ol>`.
|
|
65
33
|
|
|
66
|
-
|
|
67
|
-
|---|---|---|
|
|
68
|
-
| `...LiHTMLAttributes` | — | Все атрибуты `<li>`. |
|
|
34
|
+
### Опциональные props
|
|
69
35
|
|
|
70
|
-
|
|
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
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
91
|
-
- **`+ .orderedListItem { margin-top: 8px }`** — gap между соседними items.
|
|
92
|
-
- **`position: absolute; left: 0`** на `::before` — нумерация ровняется по левому краю; `padding-left: 25px` на `<ol>` оставляет место.
|
|
72
|
+
---
|
|
93
73
|
|
|
94
|
-
|
|
74
|
+
## Инварианты
|
|
95
75
|
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
101
|
-
- `start - 1` потому что `counter-increment` срабатывает **до** рендера `::before`, поэтому нужно начать на 1 меньше.
|
|
102
|
-
- Также пробрасывается **нативный `start` атрибут** на `<ol>` — для семантики (скринридеры, copy-paste).
|
|
93
|
+
---
|
|
103
94
|
|
|
104
|
-
|
|
95
|
+
## Accessibility
|
|
105
96
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
97
|
+
Используется семантическая разметка `<ol>` / `<li>`. Компонент не задаёт собственных
|
|
98
|
+
ARIA-атрибутов, не управляет фокусом и не обрабатывает клавиатуру. При необходимости
|
|
99
|
+
потребитель передаёт `aria-label` или `aria-labelledby` на список. Номер пункта
|
|
100
|
+
рисуется через `::before`, а текст приходит через `children`.
|
|
109
101
|
|
|
110
|
-
|
|
102
|
+
---
|
|
111
103
|
|
|
112
|
-
|
|
104
|
+
## Связанные компоненты
|
|
113
105
|
|
|
114
|
-
|
|
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
|
-
|
|
114
|
+
---
|
|
117
115
|
|
|
118
|
-
|
|
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
|
-
|
|
125
|
-
|
|
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
|
-
##
|
|
133
|
+
## История изменений
|
|
136
134
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
-
|
|
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
|
-
|
|
153
|
-
- `Text` (Typography) — типография элементов.
|
|
154
|
-
- `ETextSize.B3` — размер.
|
|
162
|
+
### WithLongContent
|
|
155
163
|
|
|
156
|
-
|
|
164
|
+
`stories/OrderedList/examples/WithLongContent.tsx`
|
|
157
165
|
|
|
158
|
-
|
|
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
|
-
|
|
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
|
-
- `
|
|
188
|
-
|
|
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
|
+
|