@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.
- package/bundle/components/Button.md +1 -0
- package/bundle/components/Checkbox.md +7 -1
- package/bundle/components/CheckboxTreeExtended.md +149 -248
- package/bundle/components/CheckboxXGroup.md +2 -0
- package/bundle/components/CheckboxYGroup.md +7 -2
- package/bundle/components/ChipGroup.md +195 -126
- package/bundle/components/LightBox.md +6 -0
- package/bundle/components/MediaWidth.md +7 -1
- package/bundle/components/Notification.md +21 -3
- package/bundle/components/SliderExtended.md +358 -222
- package/bundle/components/TabsExtended.md +380 -194
- package/bundle/components/UnorderedList.md +254 -0
- package/bundle/components/UnorderedListExtended.md +255 -0
- package/bundle/components/WindowResizeListener.md +249 -0
- package/bundle/guides/commits.md +26 -0
- package/bundle/guides/stories.md +1 -1
- package/bundle/manifest.json +23 -14
- package/bundle/release-notes/1.43.0.mdx +87 -0
- package/bundle/release-notes/1.44.0.mdx +7 -0
- package/package.json +1 -1
|
@@ -1,27 +1,25 @@
|
|
|
1
1
|
---
|
|
2
2
|
component: SliderExtended
|
|
3
|
-
category:
|
|
4
|
-
|
|
5
|
-
figma-file: ""
|
|
6
|
-
related: [Slider, SliderRange]
|
|
3
|
+
category: Slider
|
|
4
|
+
related: [Slider, SliderRange, KeyDownListener, Text]
|
|
7
5
|
tokens:
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
31
|
+
Составной слайдер: потребитель сам собирает разметку из полосы, ползунков, трека, меток и
|
|
32
|
+
подсказок и владеет значениями ползунков. Компонент отвечает за расчёт шагов, позиции
|
|
33
|
+
элементов, клавиатуру и синхронизацию частей между собой через контекст.
|
|
36
34
|
|
|
37
|
-
Используй когда:
|
|
38
|
-
|
|
39
|
-
|
|
35
|
+
Используй когда: нужен слайдер, которого нет среди готовых сборок — например с
|
|
36
|
+
нестандартным составом частей (без меток, без трека, с собственным содержимым ползунка) или
|
|
37
|
+
с подсказкой, которую нужно собрать самому.
|
|
40
38
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
## Файловая структура
|
|
39
|
+
Не используй когда:
|
|
44
40
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
##
|
|
51
|
+
## Состав
|
|
72
52
|
|
|
73
|
-
|
|
|
53
|
+
| Субкомпонент | Элемент | Роль |
|
|
74
54
|
|---|---|---|
|
|
75
|
-
| `SliderExtended
|
|
76
|
-
| `SliderExtended.
|
|
77
|
-
| `SliderExtended.Track` |
|
|
78
|
-
| `SliderExtended.
|
|
79
|
-
| `SliderExtended.
|
|
80
|
-
| `SliderExtended.
|
|
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
|
-
###
|
|
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
|
-
| `
|
|
103
|
-
| `
|
|
104
|
-
|
|
|
76
|
+
| `min` | `number` | Минимальное значение диапазона |
|
|
77
|
+
| `max` | `number` | Максимальное значение диапазона |
|
|
78
|
+
| `step` | `number \| number[]` | Длина шага либо массив значений шагов. В массиве первое значение равно `min`, последнее — `max` |
|
|
79
|
+
| `size` | `EComponentSize.MD \| EComponentSize.LG` | Размер. `SM` не поддерживается |
|
|
105
80
|
|
|
106
|
-
###
|
|
81
|
+
### SliderExtended — опциональные props
|
|
107
82
|
|
|
108
|
-
| Prop | Тип |
|
|
83
|
+
| Prop | Тип | По умолчанию | Описание |
|
|
109
84
|
|---|---|---|---|
|
|
110
|
-
| `
|
|
111
|
-
| `
|
|
85
|
+
| `disabled` | `boolean` | `false` | Слайдер не активен: части не реагируют на курсор и выпадают из табуляции |
|
|
86
|
+
| `reverse` | `boolean` | `false` | Значения возрастают справа налево |
|
|
112
87
|
|
|
113
|
-
|
|
88
|
+
`children` и остальные атрибуты `React.HTMLAttributes<HTMLDivElement>` уходят на корневой
|
|
89
|
+
элемент.
|
|
114
90
|
|
|
115
|
-
|
|
116
|
-
|---|---|---|
|
|
117
|
-
| `value` | `number` | Позиция метки на шкале. Клик по dot/text метки — ближайший Dot едет на value. |
|
|
118
|
-
| `children` | `ReactNode` | Лейбл метки (оборачивается в `Text` с размером зависимо от `size`). |
|
|
119
|
-
|
|
120
|
-
### `ISliderExtendedTooltipProps`
|
|
91
|
+
### Субкомпоненты
|
|
121
92
|
|
|
122
|
-
|
|
|
123
|
-
|
|
124
|
-
| `
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
|
129
|
-
|
|
130
|
-
|
|
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
|
-
##
|
|
132
|
+
## Дизайн-токены
|
|
135
133
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
-
|
|
142
|
-
-
|
|
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
|
-
|
|
145
|
-
`SliderExtended`
|
|
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
|
-
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
- **`
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
- **`
|
|
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
|
-
-
|
|
167
|
-
- `
|
|
168
|
-
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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` —
|
|
182
|
-
|
|
183
|
-
|
|
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` | `
|
|
195
|
-
| `Default` | `
|
|
196
|
-
| `
|
|
197
|
-
| `
|
|
198
|
-
| `
|
|
199
|
-
| `
|
|
200
|
-
| `
|
|
201
|
-
| `
|
|
202
|
-
| `
|
|
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-
|
|
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/
|
|
293
|
+
`stories/Slider/examples/SliderExtended/Default.tsx`
|
|
219
294
|
|
|
220
295
|
```tsx
|
|
221
296
|
import React, { useState } from "react";
|
|
222
|
-
import {
|
|
297
|
+
import { EComponentSize, SliderExtended } from "@sberbusiness/triplex-next";
|
|
223
298
|
|
|
224
|
-
export const
|
|
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}
|
|
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/
|
|
371
|
+
`stories/Slider/examples/SliderExtended/Range.tsx`
|
|
252
372
|
|
|
253
373
|
```tsx
|
|
254
374
|
import React, { useState } from "react";
|
|
255
|
-
import {
|
|
375
|
+
import { EComponentSize, SliderExtended } from "@sberbusiness/triplex-next";
|
|
256
376
|
|
|
257
|
-
export const
|
|
258
|
-
const [
|
|
259
|
-
const [
|
|
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 = {`[${
|
|
383
|
+
<div>values = {`[${firstValue}, ${secondValue}]`}</div>
|
|
264
384
|
<br />
|
|
265
|
-
<SliderExtended min={0} max={100} size={EComponentSize.MD}
|
|
385
|
+
<SliderExtended min={0} max={100} step={1} size={EComponentSize.MD}>
|
|
266
386
|
<SliderExtended.Rail />
|
|
267
|
-
<SliderExtended.Dot value={
|
|
387
|
+
<SliderExtended.Dot value={firstValue} onChange={setFirstValue} aria-label="Начальное значение" />
|
|
268
388
|
<SliderExtended.Track />
|
|
269
|
-
<SliderExtended.Dot value={
|
|
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/
|
|
404
|
+
`stories/Slider/examples/SliderExtended/RangeWithTooltip.tsx`
|
|
285
405
|
|
|
286
406
|
```tsx
|
|
287
407
|
import React, { useState } from "react";
|
|
288
|
-
import {
|
|
408
|
+
import { EComponentSize, SliderExtended } from "@sberbusiness/triplex-next";
|
|
289
409
|
|
|
290
|
-
export const
|
|
291
|
-
const [
|
|
292
|
-
const [
|
|
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 = {`[${
|
|
416
|
+
<div>values = {`[${firstValue}, ${secondValue}]`}</div>
|
|
297
417
|
<br />
|
|
298
|
-
<SliderExtended min={0} max={100} size={EComponentSize.MD}
|
|
418
|
+
<SliderExtended min={0} max={100} step={1} size={EComponentSize.MD}>
|
|
299
419
|
<SliderExtended.Rail />
|
|
300
|
-
<SliderExtended.Dot value={
|
|
301
|
-
<SliderExtended.Tooltip value={
|
|
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={
|
|
305
|
-
<SliderExtended.Tooltip value={
|
|
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/
|
|
441
|
+
`stories/Slider/examples/SliderExtended/WithCustomSteps.tsx`
|
|
322
442
|
|
|
323
443
|
```tsx
|
|
324
444
|
import React, { useState } from "react";
|
|
325
|
-
import {
|
|
445
|
+
import { EComponentSize, SliderExtended } from "@sberbusiness/triplex-next";
|
|
446
|
+
|
|
447
|
+
/** Шаги задаются массивом: ползунок останавливается только на этих значениях. */
|
|
448
|
+
const STEPS = [0, 25, 50, 75, 100];
|
|
326
449
|
|
|
327
|
-
export const
|
|
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}
|
|
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
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
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/
|
|
478
|
+
`stories/Slider/examples/SliderExtended/WithoutTooltip.tsx`
|
|
356
479
|
|
|
357
480
|
```tsx
|
|
358
481
|
import React, { useState } from "react";
|
|
359
|
-
import {
|
|
482
|
+
import { EComponentSize, SliderExtended } from "@sberbusiness/triplex-next";
|
|
360
483
|
|
|
361
|
-
export const
|
|
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}
|
|
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/
|
|
509
|
+
`stories/Slider/examples/SliderExtended/Disabled.tsx`
|
|
387
510
|
|
|
388
511
|
```tsx
|
|
389
512
|
import React from "react";
|
|
390
|
-
import {
|
|
513
|
+
import { EComponentSize, SliderExtended } from "@sberbusiness/triplex-next";
|
|
391
514
|
|
|
392
|
-
export const
|
|
515
|
+
export const Disabled = () => (
|
|
393
516
|
<div style={{ maxWidth: "750px", padding: "30px" }}>
|
|
394
|
-
<SliderExtended min={0} max={100} size={EComponentSize.MD}
|
|
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/
|
|
536
|
+
`stories/Slider/examples/SliderExtended/Reverse.tsx`
|
|
414
537
|
|
|
415
538
|
```tsx
|
|
416
539
|
import React, { useState } from "react";
|
|
417
|
-
import {
|
|
540
|
+
import { EComponentSize, SliderExtended } from "@sberbusiness/triplex-next";
|
|
418
541
|
|
|
419
|
-
export const
|
|
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}
|
|
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/
|
|
569
|
+
`stories/Slider/examples/SliderExtended/Amounts.tsx`
|
|
447
570
|
|
|
448
571
|
```tsx
|
|
449
572
|
import React, { useState } from "react";
|
|
450
|
-
import {
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
const [
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
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>{
|
|
614
|
+
<div>{amountFormat.format(AMOUNTS[amountIndex])}</div>
|
|
476
615
|
<br />
|
|
477
|
-
<SliderExtended min={0} max={
|
|
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={
|
|
487
|
-
<SliderExtended.Mark value={
|
|
488
|
-
<SliderExtended.Mark value={
|
|
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>
|