@sberbusiness/triplex-next-mcp-server 1.42.0-beta → 1.43.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.
@@ -1,27 +1,25 @@
1
1
  ---
2
2
  component: SliderExtended
3
- category: Form Controls
4
- figma-node: ""
5
- figma-file: ""
6
- related: [Slider, SliderRange]
3
+ category: Slider
4
+ related: [Slider, SliderRange, KeyDownListener, Text]
7
5
  tokens:
8
- - --triplex-next-Slider-Rail_Background
9
- - --triplex-next-Slider-Rail_Background_Disabled
10
- - --triplex-next-Slider-Track_Background_Default
11
- - --triplex-next-Slider-Track_Background_Hover
12
- - --triplex-next-Slider-Track_Background_Disabled
13
- - --triplex-next-Slider-Dot_Background_Default
14
- - --triplex-next-Slider-Dot_Background_Hover
15
- - --triplex-next-Slider-Dot_Background_Select
16
- - --triplex-next-Slider-Dot_Background_Disabled
17
- - --triplex-next-Slider-Dot_Inner_Background
18
- - --triplex-next-Slider-Dot_Inner_Background_Disabled
19
- - --triplex-next-Slider-Dot_Focus
20
- - --triplex-next-Slider-Mark_Dot_Background_Default
21
- - --triplex-next-Slider-Mark_Dot_Background_Selected
22
- - --triplex-next-Slider-Mark_Dot_Background_Selected_Disabled
23
- - --triplex-next-Slider-Tooltip_Background
24
- - --triplex-next-Slider-Tooltip_Color
6
+ - --triplex-next-Slider-Rail_Background
7
+ - --triplex-next-Slider-Rail_Background_Disabled
8
+ - --triplex-next-Slider-Track_Background_Default
9
+ - --triplex-next-Slider-Track_Background_Hover
10
+ - --triplex-next-Slider-Track_Background_Disabled
11
+ - --triplex-next-Slider-Dot_Background_Default
12
+ - --triplex-next-Slider-Dot_Background_Hover
13
+ - --triplex-next-Slider-Dot_Background_Select
14
+ - --triplex-next-Slider-Dot_Background_Disabled
15
+ - --triplex-next-Slider-Dot_Inner_Background
16
+ - --triplex-next-Slider-Dot_Inner_Background_Disabled
17
+ - --triplex-next-Slider-Dot_Focus
18
+ - --triplex-next-Slider-Mark_Dot_Background_Default
19
+ - --triplex-next-Slider-Mark_Dot_Background_Selected
20
+ - --triplex-next-Slider-Mark_Dot_Background_Selected_Disabled
21
+ - --triplex-next-Slider-Tooltip_Background
22
+ - --triplex-next-Slider-Tooltip_Color
25
23
  stories: stories/Slider/SliderExtended.stories.tsx
26
24
  version: "1.0"
27
25
  ---
@@ -30,157 +28,228 @@ version: "1.0"
30
28
 
31
29
  ## Назначение
32
30
 
33
- Низкоуровневая основа слайдера со slot-компонентами
34
- (`Rail`, `Dot`, `Track`, `Marks`, `Mark`, `Tooltip`). Контекст хранит состояние
35
- `dots`, `steps`, `railNode`, `focused`. Потребитель сам собирает структуру.
31
+ Составной слайдер: потребитель сам собирает разметку из полосы, ползунков, трека, меток и
32
+ подсказок и владеет значениями ползунков. Компонент отвечает за расчёт шагов, позиции
33
+ элементов, клавиатуру и синхронизацию частей между собой через контекст.
36
34
 
37
- Используй когда: нужна нестандартная компоновка (произвольное число `Dot`,
38
- кастомный `Tooltip`, своя шкала `Marks`). Для готовых сценариев — `Slider`
39
- (один ползунок) или `SliderRange` (два ползунка).
35
+ Используй когда: нужен слайдер, которого нет среди готовых сборок — например с
36
+ нестандартным составом частей (без меток, без трека, с собственным содержимым ползунка) или
37
+ с подсказкой, которую нужно собрать самому.
40
38
 
41
- ---
42
-
43
- ## Файловая структура
39
+ Не используй когда:
44
40
 
45
- ```text
46
- src/components/SliderExtended/
47
- ├── SliderExtended.tsx # FC с Provider + render children
48
- ├── SliderExtendedContext.ts # ISliderExtendedContext, ISliderExtendedDot, ISliderExtendedStep
49
- ├── SliderExtendedUtils.ts # getNormalizedValue, getNearestStep, getStepIndexByNormalizedValue...
50
- ├── components/
51
- │ ├── SliderExtendedRail.tsx
52
- │ ├── SliderExtendedMoveHandler.tsx
53
- │ ├── SliderExtendedDot/
54
- │ │ ├── SliderExtendedDot.tsx
55
- │ │ ├── SliderExtendedDotActions.ts
56
- │ │ └── useSliderExtendedDotTabIndex.tsx
57
- │ ├── SliderExtendedMarks/
58
- │ │ ├── SliderExtendedMarks.tsx
59
- │ │ ├── SliderExtendedMark.tsx
60
- │ │ └── SliderExtendedMarkActions.ts
61
- │ ├── SliderExtendedTooltip/SliderExtendedTooltip.tsx
62
- │ └── SliderExtendedTrack/
63
- │ ├── SliderExtendedTrack.tsx
64
- │ └── SliderExtendedTrackActions.ts
65
- ├── styles/
66
- └── index.ts
67
- ```
41
+ - Нужен обычный слайдер с одним ползунком — возьми `Slider`: это тот же
42
+ `SliderExtended`, уже собранный, с `marks` массивом и `renderTooltipContent`.
43
+ - Нужен выбор диапазона двумя ползунками — возьми `SliderRange`: он дополнительно следит
44
+ за порядком значений и отдаёт их отсортированной парой.
45
+ - Нужно ввести точное число — слайдер не заменяет `NumberField` / `AmountField`; при
46
+ крупных диапазонах их обычно ставят рядом.
47
+ - Нужна вертикальная ориентация или больше двух ползунков — не поддерживаются.
68
48
 
69
49
  ---
70
50
 
71
- ## Slot-компоненты
51
+ ## Состав
72
52
 
73
- | Slot | Обязателен | Назначение |
53
+ | Субкомпонент | Элемент | Роль |
74
54
  |---|---|---|
75
- | `SliderExtended.Rail` | ✅ | Полоса-подложка. Клик по rail перемещает ближайший `Dot` на ближайший `step`. |
76
- | `SliderExtended.Dot` | ✅ (≥1) | Ползунок. Управление мышью/touch/клавиатурой. |
77
- | `SliderExtended.Track` | опционально | Полоса между двумя `Dot` (закрашенная зона). Если `dots.length < 2` — не рендерит ничего. |
78
- | `SliderExtended.Marks` | опционально | Контейнер для `Mark` — шкала под rail. |
79
- | `SliderExtended.Mark` | опционально | Отдельная метка на шкале (`value`, `children`). Клик → ближайший Dot едет на value. |
80
- | `SliderExtended.Tooltip` | опционально | Тултип над Dot (рендерится внутри `<Dot>`). |
55
+ | `SliderExtended` | `div` | Контейнер и провайдер контекста. Задаёт диапазон, шаг, размер и `disabled` |
56
+ | `SliderExtended.Rail` | `div` | Полоса. Клик перемещает ближайший ползунок на ближайший шаг |
57
+ | `SliderExtended.Track` | `span` | Заполненная часть полосы. При двух ползунках перетаскивается целиком |
58
+ | `SliderExtended.Dot` | `span` | Ползунок. Контролируемый: `value` + `onChange` |
59
+ | `SliderExtended.Marks` | `div` | Контейнер меток под полосой |
60
+ | `SliderExtended.Mark` | `span` | Метка. Клик перемещает ближайший ползунок на её значение |
61
+ | `SliderExtended.Tooltip` | `div` | Подсказка над ползунком, рендерится внутри `SliderExtended.Dot` |
62
+
63
+ Порядок в разметке: `Rail` → `Dot` → `Track` → (второй `Dot`) → `Marks`. Части находят друг
64
+ друга через контекст, а не по вложенности, поэтому `Track` и `Marks` не обязаны быть
65
+ соседями `Dot` — но `Tooltip` обязан лежать внутри `Dot`, иначе он не будет показываться и
66
+ позиционироваться.
81
67
 
82
68
  ---
83
69
 
84
70
  ## Варианты и props
85
71
 
86
- ### `ISliderExtendedProps` (корневой)
87
-
88
- | Prop | Тип | Default | Описание |
89
- |---|---|---|---|
90
- | `min` | `number` | — | Минимальное значение. |
91
- | `max` | `number` | — | Максимальное значение. |
92
- | `step` | `number \| number[]` | — | Шаг или массив возможных значений (первый = `min`, последний = `max`). |
93
- | `size` | `EComponentSize.MD \| LG` | — | Только MD и LG. `SM` запрещён. |
94
- | `disabled?` | `boolean` | `false` | Блокирует интеракции. |
95
- | `reverse?` | `boolean` | `false` | Реверсивная ориентация (min справа). |
96
- | `...HTMLAttributes<HTMLDivElement>` | — | — | Прокидываются на корневой `div`. |
97
-
98
- ### `ISliderExtendedDotProps`
72
+ ### SliderExtended — обязательные props
99
73
 
100
74
  | Prop | Тип | Описание |
101
75
  |---|---|---|
102
- | `value` | `number` | Текущее значение точки. Должно принадлежать `[min, max]`. |
103
- | `onChange` | `(value: number) => void` | Вызывается при drag/keyboard/click-on-rail. |
104
- | `...HTMLAttributes<HTMLSpanElement>` | `Omit<..., "onChange" \| "value">` | Прокидываются на `<span role="slider">`. |
76
+ | `min` | `number` | Минимальное значение диапазона |
77
+ | `max` | `number` | Максимальное значение диапазона |
78
+ | `step` | `number \| number[]` | Длина шага либо массив значений шагов. В массиве первое значение равно `min`, последнее — `max` |
79
+ | `size` | `EComponentSize.MD \| EComponentSize.LG` | Размер. `SM` не поддерживается |
105
80
 
106
- ### `ISliderExtendedTrackProps`
81
+ ### SliderExtended — опциональные props
107
82
 
108
- | Prop | Тип | Default | Описание |
83
+ | Prop | Тип | По умолчанию | Описание |
109
84
  |---|---|---|---|
110
- | `draggable?` | `boolean` | `true` | Разрешает перетаскивание Track целиком. Работает только при `dots.length > 1`. |
111
- | `children` | `never` | — | Запрещено. |
85
+ | `disabled` | `boolean` | `false` | Слайдер не активен: части не реагируют на курсор и выпадают из табуляции |
86
+ | `reverse` | `boolean` | `false` | Значения возрастают справа налево |
112
87
 
113
- ### `ISliderExtendedMarkProps`
88
+ `children` и остальные атрибуты `React.HTMLAttributes<HTMLDivElement>` уходят на корневой
89
+ элемент.
114
90
 
115
- | Prop | Тип | Описание |
116
- |---|---|---|
117
- | `value` | `number` | Позиция метки на шкале. Клик по dot/text метки — ближайший Dot едет на value. |
118
- | `children` | `ReactNode` | Лейбл метки (оборачивается в `Text` с размером зависимо от `size`). |
119
-
120
- ### `ISliderExtendedTooltipProps`
91
+ ### Субкомпоненты
121
92
 
122
- | Prop | Тип | Описание |
123
- |---|---|---|
124
- | `value?` | `number` | Нужен для расчёта `offset` — сдвига тела tooltip от края, чтобы он не «вылезал» за край rail. |
125
-
126
- ### `ISliderExtendedRailProps`
127
-
128
- | Prop | Тип | Описание |
129
- |---|---|---|
130
- | `children` | `never` | Запрещено. Rail — только полоса. |
93
+ | Субкомпонент | Свои props |
94
+ |---|---|
95
+ | `SliderExtended.Dot` | `value: number` (обязателен), `onChange: (value: number) => void` (обязателен) |
96
+ | `SliderExtended.Mark` | `value: number` (обязателен) — значение, на котором стоит метка |
97
+ | `SliderExtended.Track` | `draggable?: boolean` (по умолчанию `true`) — разрешает перетаскивание трека; работает только при двух ползунках |
98
+ | `SliderExtended.Tooltip` | `value?: number` — значение ползунка; нужно только для сдвига подсказки у краёв полосы |
99
+ | `SliderExtended.Rail`, `SliderExtended.Marks` | своих props нет |
100
+
101
+ `Rail` и `Track` не принимают `children` (`children?: never`).
102
+
103
+ ### Особенности поведения
104
+
105
+ - **Значения контролируемые.** `SliderExtended.Dot` не хранит значение: он рисует позицию
106
+ по `value` и сообщает новое значение через `onChange`. Без обновления `value` снаружи
107
+ ползунок не двигается.
108
+ - **`step` числом строит шаги как `[...range(min, max, step), max]`.** Последний шаг всегда
109
+ равен `max`, даже если он не кратен шагу: при `min: 0, max: 10, step: 3` шаги равны
110
+ `0, 3, 6, 9, 10`.
111
+ - **`step` массивом задаёт произвольную шкалу.** Шаги равны визуально, а не по значению —
112
+ так делается шкала сумм с укрупняющимся шагом. Массив, собранный инлайн в JSX,
113
+ пересчитывает шаги на каждом рендере потребителя; вынеси его в константу, если это важно.
114
+ - **Пустой массив шагов (`step: []`) не рендерит ничего** — компонент возвращает `null`.
115
+ - **Значение вне диапазона не отбрасывается, а зажимается** при расчёте позиции: ползунок
116
+ встаёт на край. В `onChange` при этом уходят значения только из массива шагов.
117
+ - **Два ползунка не ограничивают друг друга.** Их значения могут пересечься, и «левый» и
118
+ «правый» определяются по текущим значениям, а не по порядку в разметке. Следить за
119
+ порядком — задача потребителя (`SliderRange` это делает).
120
+ - **Клик по полосе и по метке двигает ближайший ползунок**, а не тот, что был в фокусе.
121
+ При равном удалении выбирается первый по порядку регистрации.
122
+ - **Перетаскивание трека сдвигает оба ползунка**, сохраняя расстояние между ними, и
123
+ останавливается у краёв полосы.
124
+ - **`Tooltip` виден только при `:hover` и `:focus` ползунка** — управляется стилями, не
125
+ props. Постоянно видимой подсказки у компонента нет.
126
+ - **Ширину и внешние отступы задаёт потребитель.** Своей ширины у слайдера нет; собственные
127
+ вертикальные отступы (`padding-top` под ползунок и `padding-bottom` под метки) он
128
+ добавляет сам.
131
129
 
132
130
  ---
133
131
 
134
- ## Контекст (`ISliderExtendedContext`)
132
+ ## Дизайн-токены
135
133
 
136
- Все slot-компоненты работают через `SliderExtendedContext`. Основные поля:
137
- `addDot / removeDot / updateDot`, `dots`, `steps`, `railNode`, `focused`,
138
- `isHoverOrDragTrack`, `min`, `max`, `reverse`, `disabled`, `size`.
139
-
140
- `steps` генерируются в `useLayoutEffect` из `step` / `min` / `max`:
141
- - `step: number` → `[...range(min, max, step), max]`
142
- - `step: number[]` → используется как есть
134
+ ```text
135
+ --triplex-next-Slider-Rail_Background
136
+ --triplex-next-Slider-Rail_Background_Disabled
137
+ --triplex-next-Slider-Track_Background_Default
138
+ --triplex-next-Slider-Track_Background_Hover
139
+ --triplex-next-Slider-Track_Background_Disabled
140
+ --triplex-next-Slider-Dot_Background_Default
141
+ --triplex-next-Slider-Dot_Background_Hover
142
+ --triplex-next-Slider-Dot_Background_Select
143
+ --triplex-next-Slider-Dot_Background_Disabled
144
+ --triplex-next-Slider-Dot_Inner_Background
145
+ --triplex-next-Slider-Dot_Inner_Background_Disabled
146
+ --triplex-next-Slider-Dot_Focus
147
+ --triplex-next-Slider-Mark_Dot_Background_Default
148
+ --triplex-next-Slider-Mark_Dot_Background_Selected
149
+ --triplex-next-Slider-Mark_Dot_Background_Selected_Disabled
150
+ --triplex-next-Slider-Tooltip_Background
151
+ --triplex-next-Slider-Tooltip_Color
152
+ ```
143
153
 
144
- Нормализованное значение = `(value - min) / (max - min) * 100` (в %).
145
- `SliderExtended` рендерит `null`, пока `steps.length === 0`.
154
+ Токены общие для всего семейства слайдеров и названы с префиксом `Slider-`, а не
155
+ `SliderExtended-`: `Slider` и `SliderRange` — это тот же `SliderExtended` внутри.
156
+ Размеры (высота полосы, диаметры ползунка и точек меток) заданы литералами в
157
+ `.module.less`, z-index'ы — LESS-переменными `@slider-dot-z-index` и
158
+ `@slider-track-z-index` из `src/helpers/less/z-indexes.less`.
146
159
 
147
160
  ---
148
161
 
149
162
  ## Инварианты
150
163
 
151
- - **Порядок slot'ов в DOM важен визуально** — Rail под всем, Dot поверх Track, Marks — отдельный блок.
152
- - **`size: MD | LG`** — `SM` не поддерживается.
153
- - **`Dot.value` ∈ `[min, max]`** — валидации нет, ответственность на потребителе.
154
- - **`step: number[]`** — первое значение = `min`, последнее = `max` (контракт, не проверяется).
155
- - **Track рендерит `null`, если `dots.length < 2`** — не путать с `display:none`.
156
- - **`Track.children: never`, `Rail.children: never`** — это layout-примитивы.
157
- - **`role="slider"` на Dot, `role="button"` на Track** — не менять.
158
- - **FC, не forwardRef** на всех компонентах семейства.
159
- - **`KeyDownListener` подключается только когда компонент в фокусе** (Dot/Track) — иначе стрелки по странице будут перехватывать все слайдеры на странице.
160
- - **`throttle(handleMouseMove, 50)` / `throttle(handleMouseMove, 100)`** — значения подобраны под плавность, менять с проверкой.
164
+ - **Мутация объектов в массиве `dots` — намеренная.** `updateDot` меняет объект точки на
165
+ месте и возвращает новый массив. Обработчики перетаскивания подписываются на `document` в
166
+ момент нажатия и до конца перетаскивания видят массив `dots` того рендера — замена
167
+ объектов на новые оставила бы их с устаревшими `stepIndex` и `value`, и трек перестал бы
168
+ двигаться дальше первого шага. Не переводить на иммутабельное обновление, не разобравшись
169
+ с подпиской слушателей.
170
+ - **`forwardRef` есть у всех семи публичных компонентов** и указывает на их собственный
171
+ корневой элемент (`SliderExtended.Tooltip` — на обёртку-оверлей, а не на тело подсказки).
172
+ Не убирать.
173
+ - **`SliderExtended.Rail` обязателен для расчётов от курсора.** Позиции клика и
174
+ перетаскивания считаются от DOM-узла полосы, который попадает в контекст через
175
+ callback-ref `Rail`. Без `Rail` мышь и касание не работают, остаётся только клавиатура.
176
+ - **`SliderExtended.Track` не рендерится, пока не зарегистрирован хотя бы один ползунок**, —
177
+ на первом кадре его в DOM нет.
178
+ - **Сдвиг `SliderExtended.Tooltip` отстаёт на один рендер.** Ширина тела подсказки
179
+ (`clientWidth`) читается во время рендера, то есть берётся из уже закоммиченного DOM.
180
+ Когда меняется ширина содержимого (например `value` идёт с `5` на `100000`), рендер с
181
+ новым текстом считает сдвиг по прежней ширине, а следующего рендера не будет — узел тот
182
+ же, состояние не менялось. У краёв полосы подсказку из-за этого может подрезать.
183
+ Расчёт вынесен в чистую функцию, но входные данные приходят из прошлого кадра; чтобы
184
+ убрать отставание, нужен `ResizeObserver` на теле подсказки или запись сдвига в
185
+ runtime-CSS-переменную из layout-эффекта.
186
+ - **`disabled` и `draggable={false}` не отключают клавиатуру у `SliderExtended.Track`.**
187
+ Слушатели стрелок монтируются по одному `isFocused`, поэтому программно сфокусированный
188
+ (`.focus()` из кода потребителя) трек двигает оба ползунка даже там, где он выпал из
189
+ табуляции и не реагирует на мышь. Через UI недостижимо: `.staticSlider` и `.disabled`
190
+ дают `pointer-events: none`, а из табуляции такой трек исключён.
191
+ - **Порядок tabIndex у слайдера с двумя ползунками (0 / -1 → 1, 2, 3) — часть
192
+ accessibility-контракта**, см. раздел Accessibility. Менять только вместе с ним.
193
+ - **Собственные props субкомпонентов (`value`, `onChange`, `draggable`) и состав статических
194
+ свойств** (`Dot`, `Mark`, `Marks`, `Rail`, `Track`, `Tooltip`) — публичный API.
195
+ `ISliderProps` и `ISliderRangeProps` наследуются от `ISliderExtendedProps`, поэтому
196
+ переименование props ломает и `Slider`, и `SliderRange`.
197
+ - **Имена LESS-классов не годятся для внешних селекторов**: `generateScopedName` хеширует
198
+ их по имени папки компонента, имени класса и версии библиотеки — при каждом релизе хеш
199
+ меняется. Опираться на них можно только внутри репозитория (unit- и visual-тесты).
200
+ Класс `.sliderExtendedTooltipOverlay` при этом упомянут в модуле `SliderExtendedDot` —
201
+ так подсказка показывается по `:hover`/`:focus` ползунка; это работает, потому что оба
202
+ модуля лежат в папке `SliderExtended` и получают одинаковый хеш.
203
+ - Реализация остаётся React 17-совместимой (без `useId`, `useSyncExternalStore` и прочих
204
+ React 18-only API): код синхронизируется в `release-0`.
161
205
 
162
206
  ---
163
207
 
164
208
  ## Accessibility
165
209
 
166
- - `SliderExtended.Dot` — `<span role="slider">` с `aria-valuemin`, `aria-valuenow`, `aria-valuemax`.
167
- - `SliderExtended.Track` — `<span role="button">` (перетаскиваемый диапазон).
168
- - Keyboard:
169
- - `ArrowLeft` / `ArrowDown` — на предыдущий step.
170
- - `ArrowRight` / `ArrowUp` — на следующий step.
171
- - `preventDefault` на обработчиках — страница не скроллится при навигации.
172
- - Фокус:
173
- - `useSliderExtendedDotTabIndex` управляет `tabIndex` у Dot (в focus заходит только один Dot за раз).
174
- - `Track` получает `tabIndex={2}` только когда `dots.length > 1 && focusedSlider`.
175
- - Потребитель должен обеспечить осмысленный `aria-label` / `aria-labelledby` на root-элементе, если слайдер не в `FormField`.
210
+ - **`SliderExtended.Dot` — `role="slider"`** с `aria-valuemin`, `aria-valuenow`,
211
+ `aria-valuemax` из `min` / `value` / `max`. Текстовую подпись (`aria-label` или
212
+ `aria-labelledby`) обязан передать потребитель: библиотека мультиязычная и не хардкодит
213
+ текст. `aria-valuetext` для нелинейных шкал (суммы, диапазоны с единицами) тоже задаёт
214
+ потребитель.
215
+ - **`SliderExtended.Track` — `role="button"`**, потому что его можно перетаскивать
216
+ клавиатурой. При одном ползунке или `draggable={false}` он выпадает из табуляции.
217
+ - **Клавиатура:** `ArrowLeft` / `ArrowDown` — шаг назад, `ArrowRight` / `ArrowUp` — шаг
218
+ вперёд; для трека — шаг обоими ползунками. Обработчики вызывают `preventDefault`, чтобы
219
+ страница не скроллилась. `Home` / `End` / `PageUp` / `PageDown` не поддерживаются.
220
+ Слушатели вешаются на `window` и живут только пока элемент в фокусе.
221
+ - **Порядок табуляции у диапазона динамический.** Пока слайдер не в фокусе, в табуляцию
222
+ попадает только ползунок с меньшим `value` (`tabIndex` 0), второй исключён (`-1`). Как
223
+ только фокус попал внутрь, элементы получают `tabIndex` 1 (левый ползунок), 2 (трек),
224
+ 3 (правый ползунок) — так `Tab` идёт по слайдеру слева направо, а не выбрасывает фокус
225
+ наружу. Побочный эффект положительных `tabIndex` — эти элементы встают в начало общего
226
+ порядка табуляции страницы, пока слайдер в фокусе.
227
+ - **Метки кликабельны, но не фокусируются** и не читаются как элементы управления: это
228
+ ускоритель для мыши, дублирующий клавиатурное перемещение по шагам.
229
+ - **Подсказка не связана с ползунком через `aria`.** Она показывается по `:hover`/`:focus`
230
+ и для скринридера бесполезна — значение должно быть доступно через `aria-valuenow` или
231
+ `aria-valuetext`.
232
+ - **`disabled` только визуальный и мышиный.** Части получают `pointer-events: none` и
233
+ `tabIndex -1`, но нативного `disabled` у `span` нет: программно установленный фокус
234
+ оставляет клавиатурное перемещение работающим. Не полагайся на `disabled` как на
235
+ единственную защиту от изменения значения — источником правды остаётся `value`
236
+ потребителя.
176
237
 
177
238
  ---
178
239
 
179
240
  ## Связанные компоненты
180
241
 
181
- - `Slider` — готовый single-dot слайдер поверх SliderExtended.
182
- - `SliderRange` — готовый range-слайдер с двумя Dot.
183
- - `Tooltip`, `FormField` — часто используются рядом.
242
+ - `Slider` (`src/components/Slider/`) — готовая сборка с одним ползунком: принимает
243
+ `marks` массивом и `renderTooltipContent`, внутри рендерит тот же `SliderExtended`.
244
+ Начинай с него; `SliderExtended` нужен, когда состава `Slider` не хватает.
245
+ - `SliderRange` (`src/components/SliderRange/`) — готовая сборка на два ползунка:
246
+ хранит внутренний порядок значений, отдаёт их отсортированной парой в `onChange` и
247
+ прокидывает `draggableTrack` в `SliderExtended.Track`.
248
+ - `KeyDownListener` (`src/components/KeyDownListener/`) — `SliderExtended.Dot` и
249
+ `SliderExtended.Track` монтируют по два таких слушателя, пока элемент в фокусе; именно
250
+ через него работают стрелки.
251
+ - `Text` (`src/components/Typography/Text.tsx`) — подпись метки; размер выбирается по
252
+ размеру слайдера (`B3` для `LG`, `B4` для `MD`).
184
253
 
185
254
  ---
186
255
 
@@ -191,15 +260,21 @@ src/components/SliderExtended/
191
260
 
192
261
  | Story | Example file | Что демонстрирует |
193
262
  |---|---|---|
194
- | `Playground` | `PlaygroundExample.tsx` | Интерактивные controls базовой конфигурации |
195
- | `Default` | `DefaultExample.tsx` | Один Dot + Marks |
196
- | `Range` | `RangeExample.tsx` | Два Dot + Track |
197
- | `RangeWithTooltip` | `RangeWithTooltipExample.tsx` | Range + Tooltip на обоих Dot |
198
- | `WithCustomSteps` | `WithCustomStepsExample.tsx` | `step` как массив значений |
199
- | `WithoutTooltip` | `WithoutTooltipExample.tsx` | Минимальная конфигурация |
200
- | `Disabled` | `DisabledExample.tsx` | Состояние disabled |
201
- | `Reverse` | `ReverseExample.tsx` | Реверсивная ориентация |
202
- | `Amounts` | `AmountsExample.tsx` | Нелинейная шкала через `step: number[]` |
263
+ | `Playground` | `Playground.tsx` | Интерактивный подбор `min`, `max`, `step`, `size`, `disabled`, `reverse` |
264
+ | `Default` | `Default.tsx` | Один ползунок, метки и подсказка над ползунком |
265
+ | `Sizes` | `Sizes.tsx` | Размеры MD и LG |
266
+ | `Range` | `Range.tsx` | Два ползунка задают диапазон, трек между ними перетаскивается |
267
+ | `RangeWithTooltip` | `RangeWithTooltip.tsx` | Диапазон с подсказкой над каждым ползунком |
268
+ | `WithCustomSteps` | `WithCustomSteps.tsx` | Шаги заданы массивом значений |
269
+ | `WithoutTooltip` | `WithoutTooltip.tsx` | Ползунок без содержимого |
270
+ | `Disabled` | `Disabled.tsx` | Неактивный слайдер |
271
+ | `Reverse` | `Reverse.tsx` | Значения возрастают справа налево |
272
+ | `Amounts` | `Amounts.tsx` | Production-like шкала сумм с неравномерным шагом: слайдер работает с индексами шкалы |
273
+ | `VisualTests` | `VisualTests.tsx` | Размеры, диапазон, реверс и неактивное состояние; `play` ставит фокус на первый ползунок, чтобы попали в кадр обводка фокуса и подсказка |
274
+
275
+ `Playground` исключён из скриншот-тестов. `RangeWithTooltip` и `WithoutTooltip` тоже
276
+ исключены: подсказка скрыта, пока ползунок не в фокусе, поэтому их кадры повторяют `Range`
277
+ и `Default`.
203
278
 
204
279
  ---
205
280
 
@@ -207,7 +282,7 @@ src/components/SliderExtended/
207
282
 
208
283
  | Дата | Изменение |
209
284
  |---|---|
210
- | 2026-04-18 | Создан документ |
285
+ | 2026-08-17 | Создан документ. AI-рефакторинг (TRI-78): все семь публичных компонентов переведены с `React.FC` на `forwardRef`; шаги считаются в `useMemo` (раньше — состоянием в layout-эффекте, из-за чего первый кадр был пустым); производные состояния (позиция ползунка, `tabIndex` ползунка и трека, перетаскиваемость трека) считаются при рендере, а не через `setState` в эффектах; троттлинг перемещения перенесён внутрь `SliderExtendedMoveHandler`; `className` больше не затирает собственные классы корня и подсказки, `style` субкомпонентов больше не отбрасывается; расчёт сдвига подсказки вынесен в чистую функцию (отставание измерения на один рендер осталось — см. «Инварианты»); `tabIndex` трека считается при рендере вместо состояния в двух layout-эффектах — поведение не изменилось, `disabled` и `draggable` учитывались и раньше (на месте применения: `tabIndex={!disabled && innerDraggable ? tabIndex : -1}`); импорты переведены с `lodash` на `lodash-es`; хук `useSliderExtendedDotTabIndex` заменён на `SliderExtendedDotActions.getTabIndex`. Публичный API (props, интерфейсы, barrel-экспорты, состав статических свойств) не изменён. Добавлены unit-тесты и stories в modern pattern. |
211
286
 
212
287
  ---
213
288
 
@@ -215,22 +290,22 @@ src/components/SliderExtended/
215
290
 
216
291
  ### Default
217
292
 
218
- `stories/Slider/examples/SliderExtended/DefaultExample.tsx`
293
+ `stories/Slider/examples/SliderExtended/Default.tsx`
219
294
 
220
295
  ```tsx
221
296
  import React, { useState } from "react";
222
- import { SliderExtended, EComponentSize } from "@sberbusiness/triplex-next";
297
+ import { EComponentSize, SliderExtended } from "@sberbusiness/triplex-next";
223
298
 
224
- export const DefaultExample: React.FC = () => {
299
+ export const Default = () => {
225
300
  const [value, setValue] = useState(35);
226
301
 
227
302
  return (
228
303
  <div style={{ maxWidth: "750px", padding: "30px" }}>
229
304
  <div>value = {value}</div>
230
305
  <br />
231
- <SliderExtended min={0} max={100} size={EComponentSize.MD} step={1}>
306
+ <SliderExtended min={0} max={100} step={1} size={EComponentSize.MD}>
232
307
  <SliderExtended.Rail />
233
- <SliderExtended.Dot value={value} onChange={setValue}>
308
+ <SliderExtended.Dot value={value} onChange={setValue} aria-label="Значение">
234
309
  <SliderExtended.Tooltip value={value}>{value}</SliderExtended.Tooltip>
235
310
  </SliderExtended.Dot>
236
311
  <SliderExtended.Track />
@@ -246,27 +321,72 @@ export const DefaultExample: React.FC = () => {
246
321
  };
247
322
  ```
248
323
 
324
+ ### Sizes
325
+
326
+ `stories/Slider/examples/SliderExtended/Sizes.tsx`
327
+
328
+ ```tsx
329
+ import React, { useState } from "react";
330
+ import { EComponentSize, SliderExtended } from "@sberbusiness/triplex-next";
331
+
332
+ interface ISizeItemProps {
333
+ size: EComponentSize.MD | EComponentSize.LG;
334
+ }
335
+
336
+ const SIZES: ISizeItemProps["size"][] = [EComponentSize.MD, EComponentSize.LG];
337
+
338
+ const SizeItem = ({ size }: ISizeItemProps) => {
339
+ const [value, setValue] = useState(35);
340
+
341
+ return (
342
+ <div>
343
+ <div style={{ marginBottom: "8px", fontSize: "16px", fontWeight: "700" }}>{size.toUpperCase()}</div>
344
+ <SliderExtended min={0} max={100} step={1} size={size}>
345
+ <SliderExtended.Rail />
346
+ <SliderExtended.Dot value={value} onChange={setValue} aria-label="Значение">
347
+ <SliderExtended.Tooltip value={value}>{value}</SliderExtended.Tooltip>
348
+ </SliderExtended.Dot>
349
+ <SliderExtended.Track />
350
+ <SliderExtended.Marks>
351
+ <SliderExtended.Mark value={0}>0</SliderExtended.Mark>
352
+ <SliderExtended.Mark value={50}>50</SliderExtended.Mark>
353
+ <SliderExtended.Mark value={100}>100</SliderExtended.Mark>
354
+ </SliderExtended.Marks>
355
+ </SliderExtended>
356
+ </div>
357
+ );
358
+ };
359
+
360
+ export const Sizes = () => (
361
+ <div style={{ maxWidth: "750px", padding: "30px", display: "flex", flexDirection: "column", gap: "24px" }}>
362
+ {SIZES.map((size) => (
363
+ <SizeItem key={size} size={size} />
364
+ ))}
365
+ </div>
366
+ );
367
+ ```
368
+
249
369
  ### Range
250
370
 
251
- `stories/Slider/examples/SliderExtended/RangeExample.tsx`
371
+ `stories/Slider/examples/SliderExtended/Range.tsx`
252
372
 
253
373
  ```tsx
254
374
  import React, { useState } from "react";
255
- import { SliderExtended, EComponentSize } from "@sberbusiness/triplex-next";
375
+ import { EComponentSize, SliderExtended } from "@sberbusiness/triplex-next";
256
376
 
257
- export const RangeExample: React.FC = () => {
258
- const [value1, setValue1] = useState(35);
259
- const [value2, setValue2] = useState(60);
377
+ export const Range = () => {
378
+ const [firstValue, setFirstValue] = useState(35);
379
+ const [secondValue, setSecondValue] = useState(60);
260
380
 
261
381
  return (
262
382
  <div style={{ maxWidth: "750px", padding: "30px" }}>
263
- <div>values = {`[${value1}, ${value2}]`}</div>
383
+ <div>values = {`[${firstValue}, ${secondValue}]`}</div>
264
384
  <br />
265
- <SliderExtended min={0} max={100} size={EComponentSize.MD} step={1}>
385
+ <SliderExtended min={0} max={100} step={1} size={EComponentSize.MD}>
266
386
  <SliderExtended.Rail />
267
- <SliderExtended.Dot value={value1} onChange={setValue1} />
387
+ <SliderExtended.Dot value={firstValue} onChange={setFirstValue} aria-label="Начальное значение" />
268
388
  <SliderExtended.Track />
269
- <SliderExtended.Dot value={value2} onChange={setValue2} />
389
+ <SliderExtended.Dot value={secondValue} onChange={setSecondValue} aria-label="Конечное значение" />
270
390
  <SliderExtended.Marks>
271
391
  <SliderExtended.Mark value={0}>0</SliderExtended.Mark>
272
392
  <SliderExtended.Mark value={35}>35</SliderExtended.Mark>
@@ -281,28 +401,28 @@ export const RangeExample: React.FC = () => {
281
401
 
282
402
  ### RangeWithTooltip
283
403
 
284
- `stories/Slider/examples/SliderExtended/RangeWithTooltipExample.tsx`
404
+ `stories/Slider/examples/SliderExtended/RangeWithTooltip.tsx`
285
405
 
286
406
  ```tsx
287
407
  import React, { useState } from "react";
288
- import { SliderExtended, EComponentSize } from "@sberbusiness/triplex-next";
408
+ import { EComponentSize, SliderExtended } from "@sberbusiness/triplex-next";
289
409
 
290
- export const RangeWithTooltipExample: React.FC = () => {
291
- const [value1, setValue1] = useState(35);
292
- const [value2, setValue2] = useState(60);
410
+ export const RangeWithTooltip = () => {
411
+ const [firstValue, setFirstValue] = useState(35);
412
+ const [secondValue, setSecondValue] = useState(60);
293
413
 
294
414
  return (
295
415
  <div style={{ maxWidth: "750px", padding: "30px" }}>
296
- <div>values = {`[${value1}, ${value2}]`}</div>
416
+ <div>values = {`[${firstValue}, ${secondValue}]`}</div>
297
417
  <br />
298
- <SliderExtended min={0} max={100} size={EComponentSize.MD} step={1}>
418
+ <SliderExtended min={0} max={100} step={1} size={EComponentSize.MD}>
299
419
  <SliderExtended.Rail />
300
- <SliderExtended.Dot value={value1} onChange={setValue1}>
301
- <SliderExtended.Tooltip value={value1}>{value1}</SliderExtended.Tooltip>
420
+ <SliderExtended.Dot value={firstValue} onChange={setFirstValue} aria-label="Начальное значение">
421
+ <SliderExtended.Tooltip value={firstValue}>{firstValue}</SliderExtended.Tooltip>
302
422
  </SliderExtended.Dot>
303
423
  <SliderExtended.Track />
304
- <SliderExtended.Dot value={value2} onChange={setValue2}>
305
- <SliderExtended.Tooltip value={value2}>{value2}</SliderExtended.Tooltip>
424
+ <SliderExtended.Dot value={secondValue} onChange={setSecondValue} aria-label="Конечное значение">
425
+ <SliderExtended.Tooltip value={secondValue}>{secondValue}</SliderExtended.Tooltip>
306
426
  </SliderExtended.Dot>
307
427
  <SliderExtended.Marks>
308
428
  <SliderExtended.Mark value={0}>0</SliderExtended.Mark>
@@ -318,31 +438,34 @@ export const RangeWithTooltipExample: React.FC = () => {
318
438
 
319
439
  ### WithCustomSteps
320
440
 
321
- `stories/Slider/examples/SliderExtended/WithCustomStepsExample.tsx`
441
+ `stories/Slider/examples/SliderExtended/WithCustomSteps.tsx`
322
442
 
323
443
  ```tsx
324
444
  import React, { useState } from "react";
325
- import { SliderExtended, EComponentSize } from "@sberbusiness/triplex-next";
445
+ import { EComponentSize, SliderExtended } from "@sberbusiness/triplex-next";
446
+
447
+ /** Шаги задаются массивом: ползунок останавливается только на этих значениях. */
448
+ const STEPS = [0, 25, 50, 75, 100];
326
449
 
327
- export const WithCustomStepsExample: React.FC = () => {
450
+ export const WithCustomSteps = () => {
328
451
  const [value, setValue] = useState(50);
329
452
 
330
453
  return (
331
454
  <div style={{ maxWidth: "750px", padding: "30px" }}>
332
455
  <div>value = {value}</div>
333
456
  <br />
334
- <SliderExtended min={0} max={100} size={EComponentSize.MD} step={[0, 25, 50, 75, 100]}>
457
+ <SliderExtended min={0} max={100} step={STEPS} size={EComponentSize.MD}>
335
458
  <SliderExtended.Rail />
336
- <SliderExtended.Dot value={value} onChange={setValue}>
459
+ <SliderExtended.Dot value={value} onChange={setValue} aria-label="Значение">
337
460
  <SliderExtended.Tooltip value={value}>{value}</SliderExtended.Tooltip>
338
461
  </SliderExtended.Dot>
339
462
  <SliderExtended.Track />
340
463
  <SliderExtended.Marks>
341
- <SliderExtended.Mark value={0}>0</SliderExtended.Mark>
342
- <SliderExtended.Mark value={25}>25</SliderExtended.Mark>
343
- <SliderExtended.Mark value={50}>50</SliderExtended.Mark>
344
- <SliderExtended.Mark value={75}>75</SliderExtended.Mark>
345
- <SliderExtended.Mark value={100}>100</SliderExtended.Mark>
464
+ {STEPS.map((step) => (
465
+ <SliderExtended.Mark key={step} value={step}>
466
+ {step}
467
+ </SliderExtended.Mark>
468
+ ))}
346
469
  </SliderExtended.Marks>
347
470
  </SliderExtended>
348
471
  </div>
@@ -352,22 +475,22 @@ export const WithCustomStepsExample: React.FC = () => {
352
475
 
353
476
  ### WithoutTooltip
354
477
 
355
- `stories/Slider/examples/SliderExtended/WithoutTooltipExample.tsx`
478
+ `stories/Slider/examples/SliderExtended/WithoutTooltip.tsx`
356
479
 
357
480
  ```tsx
358
481
  import React, { useState } from "react";
359
- import { SliderExtended, EComponentSize } from "@sberbusiness/triplex-next";
482
+ import { EComponentSize, SliderExtended } from "@sberbusiness/triplex-next";
360
483
 
361
- export const WithoutTooltipExample: React.FC = () => {
484
+ export const WithoutTooltip = () => {
362
485
  const [value, setValue] = useState(35);
363
486
 
364
487
  return (
365
488
  <div style={{ maxWidth: "750px", padding: "30px" }}>
366
489
  <div>value = {value}</div>
367
490
  <br />
368
- <SliderExtended min={0} max={100} size={EComponentSize.MD} step={1}>
491
+ <SliderExtended min={0} max={100} step={1} size={EComponentSize.MD}>
369
492
  <SliderExtended.Rail />
370
- <SliderExtended.Dot value={value} onChange={setValue} />
493
+ <SliderExtended.Dot value={value} onChange={setValue} aria-label="Значение" />
371
494
  <SliderExtended.Track />
372
495
  <SliderExtended.Marks>
373
496
  <SliderExtended.Mark value={0}>0</SliderExtended.Mark>
@@ -383,17 +506,17 @@ export const WithoutTooltipExample: React.FC = () => {
383
506
 
384
507
  ### Disabled
385
508
 
386
- `stories/Slider/examples/SliderExtended/DisabledExample.tsx`
509
+ `stories/Slider/examples/SliderExtended/Disabled.tsx`
387
510
 
388
511
  ```tsx
389
512
  import React from "react";
390
- import { SliderExtended, EComponentSize } from "@sberbusiness/triplex-next";
513
+ import { EComponentSize, SliderExtended } from "@sberbusiness/triplex-next";
391
514
 
392
- export const DisabledExample: React.FC = () => (
515
+ export const Disabled = () => (
393
516
  <div style={{ maxWidth: "750px", padding: "30px" }}>
394
- <SliderExtended min={0} max={100} size={EComponentSize.MD} step={1} disabled>
517
+ <SliderExtended min={0} max={100} step={1} size={EComponentSize.MD} disabled>
395
518
  <SliderExtended.Rail />
396
- <SliderExtended.Dot value={35} onChange={() => {}}>
519
+ <SliderExtended.Dot value={35} onChange={() => {}} aria-label="Значение">
397
520
  <SliderExtended.Tooltip value={35}>35</SliderExtended.Tooltip>
398
521
  </SliderExtended.Dot>
399
522
  <SliderExtended.Track />
@@ -410,22 +533,22 @@ export const DisabledExample: React.FC = () => (
410
533
 
411
534
  ### Reverse
412
535
 
413
- `stories/Slider/examples/SliderExtended/ReverseExample.tsx`
536
+ `stories/Slider/examples/SliderExtended/Reverse.tsx`
414
537
 
415
538
  ```tsx
416
539
  import React, { useState } from "react";
417
- import { SliderExtended, EComponentSize } from "@sberbusiness/triplex-next";
540
+ import { EComponentSize, SliderExtended } from "@sberbusiness/triplex-next";
418
541
 
419
- export const ReverseExample: React.FC = () => {
542
+ export const Reverse = () => {
420
543
  const [value, setValue] = useState(35);
421
544
 
422
545
  return (
423
546
  <div style={{ maxWidth: "750px", padding: "30px" }}>
424
547
  <div>value = {value}</div>
425
548
  <br />
426
- <SliderExtended min={0} max={100} size={EComponentSize.MD} step={1} reverse>
549
+ <SliderExtended min={0} max={100} step={1} size={EComponentSize.MD} reverse>
427
550
  <SliderExtended.Rail />
428
- <SliderExtended.Dot value={value} onChange={setValue}>
551
+ <SliderExtended.Dot value={value} onChange={setValue} aria-label="Значение">
429
552
  <SliderExtended.Tooltip value={value}>{value}</SliderExtended.Tooltip>
430
553
  </SliderExtended.Dot>
431
554
  <SliderExtended.Track />
@@ -443,49 +566,62 @@ export const ReverseExample: React.FC = () => {
443
566
 
444
567
  ### Amounts
445
568
 
446
- `stories/Slider/examples/SliderExtended/AmountsExample.tsx`
569
+ `stories/Slider/examples/SliderExtended/Amounts.tsx`
447
570
 
448
571
  ```tsx
449
572
  import React, { useState } from "react";
450
- import { range } from "lodash";
451
- import { SliderExtended, EComponentSize } from "@sberbusiness/triplex-next";
452
-
453
- export const AmountsExample: React.FC = () => {
454
- const [value, setValue] = useState(1000000);
455
-
456
- const amounts = [
457
- 0,
458
- 10000,
459
- ...range(20000, 240000, 20000),
460
- ...range(240000, 1000000, 40000),
461
- ...range(1000000, 2150000, 50000),
462
- ...range(2150000, 2600000, 75000),
463
- ...range(2600000, 3000000, 100000),
464
- ...range(3000000, 3800000, 200000),
465
- ...range(3800000, 9000000, 400000),
466
- ...range(9000000, 10000000, 500000),
467
- ...range(10000000, 25000000, 2500000),
468
- ...range(25000000, 40000000, 5000000),
469
- ...range(40000000, 90000001, 10000000),
470
- 100000000,
471
- ];
573
+ import { EComponentSize, SliderExtended } from "@sberbusiness/triplex-next";
574
+
575
+ /** Возвращает значения от from до to (не включая), с шагом step. */
576
+ const range = (from: number, to: number, step: number): number[] => {
577
+ const values: number[] = [];
578
+
579
+ for (let value = from; value < to; value += step) {
580
+ values.push(value);
581
+ }
582
+
583
+ return values;
584
+ };
585
+
586
+ /**
587
+ * Шкала сумм с неравномерным шагом: чем больше сумма, тем крупнее шаг.
588
+ * Слайдер работает с индексами шкалы, поэтому шаги равны визуально.
589
+ */
590
+ const AMOUNTS = [
591
+ 0,
592
+ 10000,
593
+ ...range(20000, 240000, 20000),
594
+ ...range(240000, 1000000, 40000),
595
+ ...range(1000000, 2150000, 50000),
596
+ ...range(2150000, 2600000, 75000),
597
+ ...range(2600000, 3000000, 100000),
598
+ ...range(3000000, 3800000, 200000),
599
+ ...range(3800000, 9000000, 400000),
600
+ ...range(9000000, 10000000, 500000),
601
+ ...range(10000000, 25000000, 2500000),
602
+ ...range(25000000, 40000000, 5000000),
603
+ ...range(40000000, 90000001, 10000000),
604
+ 100000000,
605
+ ];
606
+
607
+ const amountFormat = new Intl.NumberFormat("ru-RU");
608
+
609
+ export const Amounts = () => {
610
+ const [amountIndex, setAmountIndex] = useState(AMOUNTS.indexOf(1000000));
472
611
 
473
612
  return (
474
613
  <div style={{ maxWidth: "750px", padding: "30px" }}>
475
- <div>{new Intl.NumberFormat("ru-RU").format(value)}</div>
614
+ <div>{amountFormat.format(AMOUNTS[amountIndex])}</div>
476
615
  <br />
477
- <SliderExtended min={0} max={99} size={EComponentSize.MD} step={1}>
616
+ <SliderExtended min={0} max={AMOUNTS.length - 1} step={1} size={EComponentSize.MD}>
478
617
  <SliderExtended.Rail />
479
- <SliderExtended.Dot
480
- value={amounts.findIndex((v) => value === v)}
481
- onChange={(idx) => setValue(amounts[idx])}
482
- />
618
+ <SliderExtended.Dot value={amountIndex} onChange={setAmountIndex} aria-label="Сумма" />
483
619
  <SliderExtended.Track />
484
620
  <SliderExtended.Marks>
485
621
  <SliderExtended.Mark value={0}>0 млн</SliderExtended.Mark>
486
- <SliderExtended.Mark value={32}>1 млн</SliderExtended.Mark>
487
- <SliderExtended.Mark value={65}>3 млн</SliderExtended.Mark>
488
- <SliderExtended.Mark value={99}>100 млн</SliderExtended.Mark>
622
+ <SliderExtended.Mark value={AMOUNTS.indexOf(1000000)}>1 млн</SliderExtended.Mark>
623
+ <SliderExtended.Mark value={AMOUNTS.indexOf(3000000)}>3 млн</SliderExtended.Mark>
624
+ <SliderExtended.Mark value={AMOUNTS.length - 1}>100 млн</SliderExtended.Mark>
489
625
  </SliderExtended.Marks>
490
626
  </SliderExtended>
491
627
  </div>