@sberbusiness/triplex-next-mcp-server 1.46.0-beta → 1.48.0-beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -7
- package/bundle/components/AmountField.md +222 -323
- package/bundle/components/Avatar.md +138 -146
- package/bundle/components/CardStatic.md +402 -0
- package/bundle/components/CheckboxTree.md +157 -215
- package/bundle/components/CheckboxTreeExtended.md +7 -7
- package/bundle/components/ChipGroup.md +1 -1
- package/bundle/components/ChipMonthYearPicker.md +4 -4
- package/bundle/components/ChipSuggest.md +7 -6
- package/bundle/components/CollapsibleTree.md +8 -4
- package/bundle/components/CollapsibleTreeExtended.md +8 -6
- package/bundle/components/Confirm.md +346 -281
- package/bundle/components/DateRange.md +351 -210
- package/bundle/components/FooterPage.md +1 -0
- package/bundle/components/FormField.md +11 -4
- package/bundle/components/FormGroup.md +3 -2
- package/bundle/components/LightBox.md +8 -0
- package/bundle/components/ListMaster.md +2 -2
- package/bundle/components/ListTableItem.md +6 -6
- package/bundle/components/MonthYearField.md +15 -4
- package/bundle/components/Slider.md +389 -97
- package/bundle/components/SliderRange.md +486 -105
- package/bundle/components/StatusTracker.md +386 -94
- package/bundle/components/Stepper.md +355 -102
- package/bundle/components/StepperExtended.md +633 -0
- package/bundle/components/SuggestField.md +1049 -96
- package/bundle/components/SwipeableArea.md +7 -7
- package/bundle/components/Tabs.md +265 -92
- package/bundle/components/TabsExtended.md +18 -18
- package/bundle/components/TabsLine.md +301 -127
- package/bundle/components/TagColor.md +187 -66
- package/bundle/components/TagGroup.md +184 -48
- package/bundle/components/TextareaField.md +278 -70
- package/bundle/components/TopOverlay.md +17 -1
- package/bundle/components/TreeView.md +16 -7
- package/bundle/components/TriggerClickOnKeyDownEvent.md +277 -0
- package/bundle/components/UploadZone.md +318 -0
- package/bundle/guides/codestyle.md +16 -1
- package/bundle/guides/commits.md +47 -1
- package/bundle/guides/tests.md +23 -2
- package/bundle/manifest.json +48 -21
- package/bundle/release-notes/1.47.0.mdx +115 -0
- package/bundle/release-notes/1.48.0.mdx +68 -0
- package/bundle/release-notes/1.49.0.mdx +7 -0
- package/bundle/tokens.json +3 -3
- package/dist/index.js +220 -6
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
component: SliderRange
|
|
3
|
-
category:
|
|
4
|
-
figma-node: ""
|
|
5
|
-
figma-file: ""
|
|
3
|
+
category: Slider
|
|
6
4
|
related: [Slider, SliderExtended]
|
|
7
5
|
tokens: []
|
|
8
6
|
stories: stories/Slider/SliderRange.stories.tsx
|
|
@@ -13,148 +11,531 @@ version: "1.0"
|
|
|
13
11
|
|
|
14
12
|
## Назначение
|
|
15
13
|
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
Слайдер с двумя ползунками — готовая сборка `SliderExtended` для выбора диапазона: полоса,
|
|
15
|
+
два ползунка, трек между ними и метки уже собраны, потребителю остаётся задать границы, шаг,
|
|
16
|
+
метки и владеть парой значений.
|
|
18
17
|
|
|
19
|
-
Используй когда:
|
|
20
|
-
|
|
18
|
+
Используй когда: пользователь выбирает диапазон из двух чисел и точность ввода не важна —
|
|
19
|
+
сумма «от и до», срок, процентный коридор.
|
|
21
20
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
## Файловая структура
|
|
21
|
+
Не используй когда:
|
|
25
22
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
23
|
+
- Нужен один ползунок — возьми `Slider`.
|
|
24
|
+
- Нужен нестандартный состав частей (без меток, без трека, своё содержимое ползунка) или
|
|
25
|
+
собственная разметка подсказки — собери слайдер из `SliderExtended`.
|
|
26
|
+
- Нужно ввести точные границы — слайдер не заменяет пару `NumberField` / `AmountField`; при
|
|
27
|
+
крупных диапазонах их обычно ставят рядом со слайдером.
|
|
28
|
+
- Ползункам нужны текстовые подписи для скринридера — `SliderRange` их передать не
|
|
29
|
+
позволяет (см. Accessibility), собирай из `SliderExtended`.
|
|
32
30
|
|
|
33
31
|
---
|
|
34
32
|
|
|
35
33
|
## Варианты и props
|
|
36
34
|
|
|
37
|
-
###
|
|
35
|
+
### Обязательные props
|
|
36
|
+
|
|
37
|
+
| Prop | Тип | Описание |
|
|
38
|
+
|---|---|---|
|
|
39
|
+
| `min` | `number` | Минимальное значение диапазона |
|
|
40
|
+
| `max` | `number` | Максимальное значение диапазона |
|
|
41
|
+
| `size` | `EComponentSize.MD \| EComponentSize.LG` | Размер. `SM` не поддерживается |
|
|
42
|
+
| `values` | `TSliderRangeValues` (`[number, number]`) | Текущая пара значений |
|
|
43
|
+
| `onChange` | `(values: TSliderRangeValues) => void` | Вызывается с отсортированной парой значений |
|
|
44
|
+
| `marks` | `ISliderRangeMark[]` | Метки под полосой: `{ value: number; label: React.ReactNode }` |
|
|
38
45
|
|
|
39
|
-
|
|
46
|
+
### Опциональные props
|
|
40
47
|
|
|
41
|
-
| Prop | Тип |
|
|
48
|
+
| Prop | Тип | По умолчанию | Описание |
|
|
42
49
|
|---|---|---|---|
|
|
43
|
-
| `
|
|
44
|
-
| `
|
|
45
|
-
| `
|
|
46
|
-
| `
|
|
47
|
-
| `
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
50
|
+
| `step` | `number \| number[]` | `1` | Длина шага либо массив значений шагов. В массиве первое значение равно `min`, последнее — `max` |
|
|
51
|
+
| `draggableTrack` | `boolean` | `true` | Трек между ползунками перетаскивается целиком и сдвигает оба ползунка |
|
|
52
|
+
| `reverse` | `boolean` | `false` | Значения возрастают справа налево |
|
|
53
|
+
| `disabled` | `boolean` | `false` | Слайдер не активен: части не реагируют на курсор и выпадают из табуляции |
|
|
54
|
+
| `renderTooltipContent` | `(value: number) => React.ReactNode` | — | Содержимое подсказки над ползунком; вызывается для каждого ползунка со своим значением. Без этого prop подсказки не рендерятся вовсе |
|
|
55
|
+
|
|
56
|
+
Остальные атрибуты `React.HTMLAttributes<HTMLDivElement>` (включая `className`, `id`,
|
|
57
|
+
`aria-*`, `data-*`) уходят на **корневой `div`** слайдера. `children` запрещён
|
|
58
|
+
(`children?: never`): разметку собирает сам компонент.
|
|
59
|
+
|
|
60
|
+
### Особенности поведения
|
|
61
|
+
|
|
62
|
+
- **Компонент двигает ползунки сам.** Он рисует их по внутренним значениям и сообщает новую
|
|
63
|
+
пару через `onChange`. `values` задаёт значения при монтировании и перезаписывает их, когда
|
|
64
|
+
потребитель присылает новую пару. Поэтому ползунки сдвинутся, даже если потребитель
|
|
65
|
+
проигнорирует `onChange`, — но тогда показанный диапазон разойдётся с его `values`.
|
|
66
|
+
Обновляй `values` в `onChange`, иначе состояние на экране и в потребителе рассинхронизируются.
|
|
67
|
+
- **Наружу пара всегда уходит отсортированной.** Если ползунки поменялись местами,
|
|
68
|
+
`onChange` получает `[меньшее, большее]`, а внутри компонент помнит, какой ползунок
|
|
69
|
+
какой (см. «Инварианты» про `innerValues`).
|
|
70
|
+
- **Неотсортированные `values` компонент исправляет сам.** Если `values[0] > values[1]`,
|
|
71
|
+
при монтировании и на каждом обновлении вызывается `onChange` с отсортированной парой.
|
|
72
|
+
Потребитель, который проигнорирует этот вызов и продолжит присылать перевёрнутую пару,
|
|
73
|
+
получит его снова на следующем обновлении; потребитель, который в ответ на него запишет
|
|
74
|
+
в state новую перевёрнутую пару, зациклит обновления.
|
|
75
|
+
- **Перемещение трека меняет оба ползунка и вызывает `onChange` дважды подряд** — по
|
|
76
|
+
вызову на ползунок. Первый вызов приходит с промежуточной парой, второй — с итоговой.
|
|
77
|
+
Это же верно для стрелок на сфокусированном треке.
|
|
78
|
+
- **`step` числом строит шаги как `[...range(min, max, step), max]`.** Последний шаг всегда
|
|
79
|
+
равен `max`, даже если он не кратен шагу: при `min: 0, max: 10, step: 3` шаги равны
|
|
80
|
+
`0, 3, 6, 9, 10`.
|
|
81
|
+
- **`step` массивом задаёт произвольную шкалу.** Шаги равны визуально, а не по значению —
|
|
82
|
+
так делается шкала сумм с укрупняющимся шагом (см. story `Amounts`). Массив, собранный
|
|
83
|
+
инлайн в JSX, пересчитывает шаги на каждом рендере потребителя; вынеси его в константу.
|
|
84
|
+
- **Ползунки могут стоять в одной точке**, на один шаг они не «раздвигаются»: минимальной
|
|
85
|
+
ширины диапазона у компонента нет.
|
|
86
|
+
- **Клик по полосе и по метке двигает ближайший ползунок** на ближайший шаг / на значение
|
|
87
|
+
метки.
|
|
88
|
+
- **`marks` не обязаны совпадать с шагами.** Метка со значением вне `[min, max]`
|
|
89
|
+
прижимается к краю полосы; `value` метки используется как React-ключ, поэтому дубли
|
|
90
|
+
значений дадут предупреждение о неуникальных ключах.
|
|
91
|
+
- **Подсказки видны только при `:hover` и `:focus` ползунка** — управляются стилями, не
|
|
92
|
+
props. Постоянно видимой подсказки нет, поэтому текущий диапазон обычно дублируют рядом
|
|
93
|
+
со слайдером (так сделано в большинстве stories).
|
|
94
|
+
- **Ширину и внешние отступы задаёт потребитель.** Своей ширины у слайдера нет; собственные
|
|
95
|
+
вертикальные отступы (под ползунки и под метки) он добавляет сам.
|
|
96
|
+
|
|
97
|
+
---
|
|
59
98
|
|
|
60
|
-
|
|
61
|
-
`[min, max]`. Внутри (во время drag) значения могут «переставляться» — это
|
|
62
|
-
состояние `innerValues` и наружу не видно.
|
|
99
|
+
## Дизайн-токены
|
|
63
100
|
|
|
64
|
-
|
|
101
|
+
Собственных токенов у компонента нет: своих стилей `SliderRange` не имеет и целиком состоит
|
|
102
|
+
из `SliderExtended`. Внешний вид настраивается токенами группы `Slider` — их список и
|
|
103
|
+
порядок переопределения см. в `SliderExtended-ai.md` → «Дизайн-токены». Значения по
|
|
104
|
+
умолчанию — `src/components/DesignTokens/components/Slider.ts`.
|
|
65
105
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Инварианты
|
|
69
109
|
|
|
70
|
-
`
|
|
110
|
+
- **`SliderRange` — class-компонент, и это часть публичного API.** `ref` на нём даёт
|
|
111
|
+
инстанс класса, а не корневой `div` (в отличие от `Slider`, который обёрнут в
|
|
112
|
+
`React.forwardRef`). Перевод в функциональный компонент или оборачивание в `forwardRef`
|
|
113
|
+
сменит ref-target и тип компонента — breaking change.
|
|
114
|
+
- **Внутреннее состояние `innerValues` хранит значения в порядке ползунков, а не по
|
|
115
|
+
возрастанию.** Так левый ползунок остаётся левым после того, как его перетащили за
|
|
116
|
+
правый: наружу пара уходит отсортированной, а внутри порядок сохраняется. Синхронизация с
|
|
117
|
+
`values` в `componentDidUpdate` учитывает это — когда ползунки перекрещены, новые `values`
|
|
118
|
+
присваиваются в обратном порядке. Замена `innerValues` на отсортированный массив заставит
|
|
119
|
+
ползунки перепрыгивать во время перетаскивания.
|
|
120
|
+
- **`onChange` вызывается внутри updater'а `setState` намеренно.** Перемещение трека меняет
|
|
121
|
+
оба ползунка двумя вызовами подряд в одном батче, и второй обязан видеть значение,
|
|
122
|
+
записанное первым, — иначе трек сдвинет только один ползунок. Побочный эффект: в
|
|
123
|
+
`StrictMode` двойной вызов updater'а приводит к двойному вызову `onChange` с теми же
|
|
124
|
+
значениями.
|
|
125
|
+
- **Пары значений сравниваются по склейке (`values.join("")`).** Из-за этого пары вроде
|
|
126
|
+
`[1, 23]` и `[12, 3]` считаются одинаковыми, и переход между ними не обновит ползунки.
|
|
127
|
+
Поведение историческое; чинить его нужно осознанно и вместе с release notes, потому что
|
|
128
|
+
оно наблюдаемо.
|
|
129
|
+
- **`step` по умолчанию задан через `defaultProps`** (`1`). Значение по умолчанию —
|
|
130
|
+
часть публичного контракта.
|
|
131
|
+
- **`children?: never`** — состав слайдера фиксирован, расширяется он только через
|
|
132
|
+
`SliderExtended`.
|
|
133
|
+
- **`ISliderRangeProps` наследуется от `ISliderExtendedProps`**
|
|
134
|
+
(`Omit<..., "onChange" | "step">`). Переименование props в `SliderExtended` ломает
|
|
135
|
+
публичный API `SliderRange` — менять только вместе.
|
|
136
|
+
- **Порядок частей в разметке (`Rail` → `Dot` → `Track` → `Dot` → `Marks`) и наличие `Rail`
|
|
137
|
+
обязательны.** Позиции клика и перетаскивания считаются от DOM-узла полосы; без `Rail`
|
|
138
|
+
работает только клавиатура. `Tooltip` обязан лежать внутри `Dot`.
|
|
139
|
+
- **`renderTooltipContent` вызывается на каждом рендере** для каждого ползунка — держи её
|
|
140
|
+
дешёвой и без side effects.
|
|
141
|
+
- Реализация остаётся React 17-совместимой (без `useId`, `useSyncExternalStore` и прочих
|
|
142
|
+
React 18-only API): код синхронизируется в `release-0`.
|
|
71
143
|
|
|
72
144
|
---
|
|
73
145
|
|
|
74
|
-
##
|
|
146
|
+
## Accessibility
|
|
75
147
|
|
|
76
|
-
|
|
148
|
+
- **Каждый ползунок — `role="slider"`** с `aria-valuemin`, `aria-valuenow`, `aria-valuemax`
|
|
149
|
+
из `min` / значения ползунка / `max` (рендерится `SliderExtended.Dot`).
|
|
150
|
+
- **Подписи ползункам `SliderRange` передать нельзя.** Все лишние props уходят на корневой
|
|
151
|
+
`div`, а не на ползунки, поэтому `aria-label` / `aria-labelledby` окажутся на контейнере
|
|
152
|
+
без роли. Если подписи «от» и «до» или `aria-valuetext` (нелинейная шкала, суммы, единицы
|
|
153
|
+
измерения) обязательны — собирай слайдер из `SliderExtended` и передавай атрибуты прямо в
|
|
154
|
+
`SliderExtended.Dot`.
|
|
155
|
+
- **Трек — `role="button"`**, потому что его можно перетаскивать клавиатурой. При
|
|
156
|
+
`draggableTrack={false}` он выпадает из табуляции.
|
|
157
|
+
- **Клавиатура:** `ArrowLeft` / `ArrowDown` — шаг назад, `ArrowRight` / `ArrowUp` — шаг
|
|
158
|
+
вперёд; на треке — шаг обоими ползунками сразу. Обработчики вызывают `preventDefault`,
|
|
159
|
+
чтобы страница не скроллилась. `Home` / `End` / `PageUp` / `PageDown` не поддерживаются.
|
|
160
|
+
- **Порядок табуляции динамический.** Пока слайдер не в фокусе, в табуляцию попадает только
|
|
161
|
+
ползунок с меньшим значением (`tabIndex` 0), второй исключён (`-1`). Как только фокус
|
|
162
|
+
попал внутрь, элементы получают `tabIndex` 1 (левый ползунок), 2 (трек), 3 (правый
|
|
163
|
+
ползунок). Побочный эффект положительных `tabIndex` — эти элементы встают в начало общего
|
|
164
|
+
порядка табуляции страницы, пока слайдер в фокусе.
|
|
165
|
+
- **Метки кликабельны, но не фокусируются** и не читаются как элементы управления: это
|
|
166
|
+
ускоритель для мыши, дублирующий клавиатурное перемещение по шагам.
|
|
167
|
+
- **Подсказки не связаны с ползунками через `aria`** — они показываются по `:hover`/`:focus`
|
|
168
|
+
и для скринридера бесполезны; значения доступны через `aria-valuenow`.
|
|
169
|
+
- **`disabled` только визуальный и мышиный.** Части получают `pointer-events: none` и
|
|
170
|
+
`tabIndex -1`, но нативного `disabled` у `span` нет: программно установленный фокус
|
|
171
|
+
оставляет клавиатурное перемещение работающим — стрелки двигают ползунки и вызывают
|
|
172
|
+
`onChange` так же, как у активного слайдера.
|
|
77
173
|
|
|
78
|
-
|
|
79
|
-
Причина: хранение `innerValues` и логика `validateValues` / `componentDidUpdate`
|
|
80
|
-
со сравнением предыдущих props по `values.join("")`.
|
|
174
|
+
---
|
|
81
175
|
|
|
82
|
-
|
|
176
|
+
## Связанные компоненты
|
|
83
177
|
|
|
84
|
-
-
|
|
85
|
-
`
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
178
|
+
- `SliderExtended` (`src/components/SliderExtended/`) — составной слайдер, из которого
|
|
179
|
+
собран `SliderRange`, и источник его props. Нужен, когда состава `SliderRange` не
|
|
180
|
+
хватает: свой набор частей, своя подсказка, `aria`-атрибуты на ползунках.
|
|
181
|
+
- `Slider` (`src/components/Slider/`) — тот же слайдер на один ползунок: принимает `value`
|
|
182
|
+
числом и не следит за порядком значений.
|
|
89
183
|
|
|
90
|
-
|
|
184
|
+
---
|
|
91
185
|
|
|
92
|
-
|
|
93
|
-
с отсортированным массивом. Конструктор и `componentDidUpdate` вызывают
|
|
94
|
-
валидацию на каждом rerender.
|
|
186
|
+
## Stories
|
|
95
187
|
|
|
96
|
-
|
|
188
|
+
Основные истории: `stories/Slider/SliderRange.stories.tsx`
|
|
189
|
+
Файлы примеров: `stories/Slider/examples/SliderRange/`
|
|
190
|
+
|
|
191
|
+
| Story | Example file | Что демонстрирует |
|
|
192
|
+
|---|---|---|
|
|
193
|
+
| `Playground` | `Playground.tsx` | Интерактивный подбор `min`, `max`, `step`, `marks`, `size`, `draggableTrack`, `disabled`, `reverse` |
|
|
194
|
+
| `Default` | `Default.tsx` | Диапазон с метками и шагом по умолчанию |
|
|
195
|
+
| `Sizes` | `Sizes.tsx` | Размеры MD и LG |
|
|
196
|
+
| `WithTooltip` | `WithTooltip.tsx` | Подсказки над обоими ползунками через `renderTooltipContent` |
|
|
197
|
+
| `WithCustomSteps` | `WithCustomSteps.tsx` | Шаги заданы массивом значений |
|
|
198
|
+
| `NonDraggableTrack` | `NonDraggableTrack.tsx` | `draggableTrack={false}` — трек не перетаскивается |
|
|
199
|
+
| `Disabled` | `Disabled.tsx` | Неактивный слайдер |
|
|
200
|
+
| `Reverse` | `Reverse.tsx` | Значения возрастают справа налево |
|
|
201
|
+
| `Amounts` | `Amounts.tsx` | Production-like шкала сумм с неравномерным шагом: слайдер работает с индексами шкалы |
|
|
202
|
+
| `VisualTests` | `VisualTests.tsx` | Размеры, шаги массивом, ползунки в одной точке, реверс и неактивное состояние; `play` ставит фокус на левый ползунок, чтобы в кадр попали обводка фокуса и подсказка |
|
|
203
|
+
|
|
204
|
+
`Playground` исключён из скриншот-тестов. `WithTooltip` исключён, потому что подсказки
|
|
205
|
+
скрыты, пока ползунок не в фокусе (состояние с подсказкой снимает `VisualTests`), а
|
|
206
|
+
`NonDraggableTrack` — потому что `draggableTrack` в статике неотличим от `Default`.
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## История изменений
|
|
211
|
+
|
|
212
|
+
| Дата | Изменение |
|
|
213
|
+
|---|---|
|
|
214
|
+
| 2026-09-15 | Создан документ. AI-рефакторинг (TRI-79): props задокументированы JSDoc, сортировка и сравнение значений вынесены в чистые функции модуля, синхронизация `values` → `innerValues` упрощена, из разметки убран избыточный проброс `size` (значение приходит тем же spread'ом). Публичный API props, интерфейсов и barrel-экспортов не изменён, компонент остался классом. Добавлены unit-тесты, stories переведены на modern pattern. |
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## Примеры использования
|
|
219
|
+
|
|
220
|
+
### Default
|
|
221
|
+
|
|
222
|
+
`stories/Slider/examples/SliderRange/Default.tsx`
|
|
97
223
|
|
|
98
224
|
```tsx
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
225
|
+
import React, { useState } from "react";
|
|
226
|
+
import { EComponentSize, SliderRange, TSliderRangeValues } from "@sberbusiness/triplex-next";
|
|
227
|
+
|
|
228
|
+
const MARKS = [
|
|
229
|
+
{ value: 0, label: "0" },
|
|
230
|
+
{ value: 30, label: "30" },
|
|
231
|
+
{ value: 70, label: "70" },
|
|
232
|
+
{ value: 100, label: "100" },
|
|
233
|
+
];
|
|
234
|
+
|
|
235
|
+
export const Default = () => {
|
|
236
|
+
const [values, setValues] = useState<TSliderRangeValues>([30, 70]);
|
|
237
|
+
|
|
238
|
+
return (
|
|
239
|
+
<div style={{ maxWidth: "750px", padding: "30px" }}>
|
|
240
|
+
<div>values = [{values.join(", ")}]</div>
|
|
241
|
+
<br />
|
|
242
|
+
<SliderRange
|
|
243
|
+
min={0}
|
|
244
|
+
max={100}
|
|
245
|
+
size={EComponentSize.MD}
|
|
246
|
+
marks={MARKS}
|
|
247
|
+
values={values}
|
|
248
|
+
onChange={setValues}
|
|
249
|
+
/>
|
|
250
|
+
</div>
|
|
251
|
+
);
|
|
252
|
+
};
|
|
112
253
|
```
|
|
113
254
|
|
|
114
|
-
|
|
255
|
+
### Sizes
|
|
115
256
|
|
|
116
|
-
|
|
257
|
+
`stories/Slider/examples/SliderRange/Sizes.tsx`
|
|
117
258
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
259
|
+
```tsx
|
|
260
|
+
import React, { useState } from "react";
|
|
261
|
+
import { EComponentSize, SliderRange, TSliderRangeValues } from "@sberbusiness/triplex-next";
|
|
262
|
+
|
|
263
|
+
const MARKS = [
|
|
264
|
+
{ value: 0, label: "0" },
|
|
265
|
+
{ value: 50, label: "50" },
|
|
266
|
+
{ value: 100, label: "100" },
|
|
267
|
+
];
|
|
268
|
+
|
|
269
|
+
interface ISizeItemProps {
|
|
270
|
+
size: EComponentSize.MD | EComponentSize.LG;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const SIZES: ISizeItemProps["size"][] = [EComponentSize.MD, EComponentSize.LG];
|
|
274
|
+
|
|
275
|
+
const SizeItem = ({ size }: ISizeItemProps) => {
|
|
276
|
+
const [values, setValues] = useState<TSliderRangeValues>([30, 70]);
|
|
277
|
+
|
|
278
|
+
return (
|
|
279
|
+
<div>
|
|
280
|
+
<div style={{ marginBottom: "8px", fontSize: "16px", fontWeight: "700" }}>{size.toUpperCase()}</div>
|
|
281
|
+
<SliderRange min={0} max={100} size={size} marks={MARKS} values={values} onChange={setValues} />
|
|
282
|
+
</div>
|
|
283
|
+
);
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
export const Sizes = () => (
|
|
287
|
+
<div style={{ maxWidth: "750px", padding: "30px", display: "flex", flexDirection: "column", gap: "24px" }}>
|
|
288
|
+
{SIZES.map((size) => (
|
|
289
|
+
<SizeItem key={size} size={size} />
|
|
290
|
+
))}
|
|
291
|
+
</div>
|
|
292
|
+
);
|
|
293
|
+
```
|
|
128
294
|
|
|
129
|
-
|
|
295
|
+
### WithTooltip
|
|
130
296
|
|
|
131
|
-
|
|
297
|
+
`stories/Slider/examples/SliderRange/WithTooltip.tsx`
|
|
132
298
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
299
|
+
```tsx
|
|
300
|
+
import React, { useState } from "react";
|
|
301
|
+
import { EComponentSize, SliderRange, TSliderRangeValues } from "@sberbusiness/triplex-next";
|
|
302
|
+
|
|
303
|
+
const MARKS = [
|
|
304
|
+
{ value: 0, label: "0" },
|
|
305
|
+
{ value: 35, label: "35" },
|
|
306
|
+
{ value: 66, label: "66" },
|
|
307
|
+
{ value: 100, label: "100" },
|
|
308
|
+
];
|
|
309
|
+
|
|
310
|
+
export const WithTooltip = () => {
|
|
311
|
+
const [values, setValues] = useState<TSliderRangeValues>([35, 66]);
|
|
312
|
+
|
|
313
|
+
return (
|
|
314
|
+
<div style={{ maxWidth: "750px", padding: "30px" }}>
|
|
315
|
+
<div>values = [{values.join(", ")}]</div>
|
|
316
|
+
<br />
|
|
317
|
+
<SliderRange
|
|
318
|
+
min={0}
|
|
319
|
+
max={100}
|
|
320
|
+
size={EComponentSize.MD}
|
|
321
|
+
marks={MARKS}
|
|
322
|
+
values={values}
|
|
323
|
+
onChange={setValues}
|
|
324
|
+
renderTooltipContent={(value) => `${value} %`}
|
|
325
|
+
/>
|
|
326
|
+
</div>
|
|
327
|
+
);
|
|
328
|
+
};
|
|
329
|
+
```
|
|
139
330
|
|
|
140
|
-
|
|
331
|
+
### WithCustomSteps
|
|
141
332
|
|
|
142
|
-
|
|
333
|
+
`stories/Slider/examples/SliderRange/WithCustomSteps.tsx`
|
|
143
334
|
|
|
144
|
-
|
|
145
|
-
|
|
335
|
+
```tsx
|
|
336
|
+
import React, { useState } from "react";
|
|
337
|
+
import { EComponentSize, SliderRange, TSliderRangeValues } from "@sberbusiness/triplex-next";
|
|
338
|
+
|
|
339
|
+
// Массив шагов вынесен из JSX: инлайновый массив пересчитывает шаги на каждом рендере.
|
|
340
|
+
const STEPS = [0, 25, 50, 75, 100];
|
|
341
|
+
|
|
342
|
+
const MARKS = STEPS.map((step) => ({ value: step, label: String(step) }));
|
|
343
|
+
|
|
344
|
+
export const WithCustomSteps = () => {
|
|
345
|
+
const [values, setValues] = useState<TSliderRangeValues>([25, 75]);
|
|
346
|
+
|
|
347
|
+
return (
|
|
348
|
+
<div style={{ maxWidth: "750px", padding: "30px" }}>
|
|
349
|
+
<div>values = [{values.join(", ")}]</div>
|
|
350
|
+
<br />
|
|
351
|
+
<SliderRange
|
|
352
|
+
min={0}
|
|
353
|
+
max={100}
|
|
354
|
+
step={STEPS}
|
|
355
|
+
size={EComponentSize.MD}
|
|
356
|
+
marks={MARKS}
|
|
357
|
+
values={values}
|
|
358
|
+
onChange={setValues}
|
|
359
|
+
/>
|
|
360
|
+
</div>
|
|
361
|
+
);
|
|
362
|
+
};
|
|
363
|
+
```
|
|
146
364
|
|
|
147
|
-
|
|
365
|
+
### NonDraggableTrack
|
|
148
366
|
|
|
149
|
-
|
|
367
|
+
`stories/Slider/examples/SliderRange/NonDraggableTrack.tsx`
|
|
150
368
|
|
|
151
|
-
|
|
152
|
-
|
|
369
|
+
```tsx
|
|
370
|
+
import React, { useState } from "react";
|
|
371
|
+
import { EComponentSize, SliderRange, TSliderRangeValues } from "@sberbusiness/triplex-next";
|
|
372
|
+
|
|
373
|
+
const MARKS = [
|
|
374
|
+
{ value: 0, label: "0" },
|
|
375
|
+
{ value: 35, label: "35" },
|
|
376
|
+
{ value: 66, label: "66" },
|
|
377
|
+
{ value: 100, label: "100" },
|
|
378
|
+
];
|
|
379
|
+
|
|
380
|
+
export const NonDraggableTrack = () => {
|
|
381
|
+
const [values, setValues] = useState<TSliderRangeValues>([35, 66]);
|
|
382
|
+
|
|
383
|
+
return (
|
|
384
|
+
<div style={{ maxWidth: "750px", padding: "30px" }}>
|
|
385
|
+
<div>values = [{values.join(", ")}]</div>
|
|
386
|
+
<br />
|
|
387
|
+
<SliderRange
|
|
388
|
+
min={0}
|
|
389
|
+
max={100}
|
|
390
|
+
size={EComponentSize.MD}
|
|
391
|
+
marks={MARKS}
|
|
392
|
+
values={values}
|
|
393
|
+
onChange={setValues}
|
|
394
|
+
draggableTrack={false}
|
|
395
|
+
/>
|
|
396
|
+
</div>
|
|
397
|
+
);
|
|
398
|
+
};
|
|
399
|
+
```
|
|
153
400
|
|
|
154
|
-
|
|
401
|
+
### Disabled
|
|
155
402
|
|
|
156
|
-
|
|
403
|
+
`stories/Slider/examples/SliderRange/Disabled.tsx`
|
|
404
|
+
|
|
405
|
+
```tsx
|
|
406
|
+
import React from "react";
|
|
407
|
+
import { EComponentSize, SliderRange } from "@sberbusiness/triplex-next";
|
|
408
|
+
|
|
409
|
+
const MARKS = [
|
|
410
|
+
{ value: 0, label: "0" },
|
|
411
|
+
{ value: 35, label: "35" },
|
|
412
|
+
{ value: 66, label: "66" },
|
|
413
|
+
{ value: 100, label: "100" },
|
|
414
|
+
];
|
|
415
|
+
|
|
416
|
+
export const Disabled = () => (
|
|
417
|
+
<div style={{ maxWidth: "750px", padding: "30px" }}>
|
|
418
|
+
<SliderRange
|
|
419
|
+
min={0}
|
|
420
|
+
max={100}
|
|
421
|
+
size={EComponentSize.MD}
|
|
422
|
+
marks={MARKS}
|
|
423
|
+
values={[35, 66]}
|
|
424
|
+
onChange={() => {}}
|
|
425
|
+
disabled
|
|
426
|
+
/>
|
|
427
|
+
</div>
|
|
428
|
+
);
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
### Reverse
|
|
432
|
+
|
|
433
|
+
`stories/Slider/examples/SliderRange/Reverse.tsx`
|
|
434
|
+
|
|
435
|
+
```tsx
|
|
436
|
+
import React, { useState } from "react";
|
|
437
|
+
import { EComponentSize, SliderRange, TSliderRangeValues } from "@sberbusiness/triplex-next";
|
|
438
|
+
|
|
439
|
+
const MARKS = [
|
|
440
|
+
{ value: 0, label: "0" },
|
|
441
|
+
{ value: 35, label: "35" },
|
|
442
|
+
{ value: 66, label: "66" },
|
|
443
|
+
{ value: 100, label: "100" },
|
|
444
|
+
];
|
|
445
|
+
|
|
446
|
+
export const Reverse = () => {
|
|
447
|
+
const [values, setValues] = useState<TSliderRangeValues>([35, 66]);
|
|
448
|
+
|
|
449
|
+
return (
|
|
450
|
+
<div style={{ maxWidth: "750px", padding: "30px" }}>
|
|
451
|
+
<div>values = [{values.join(", ")}]</div>
|
|
452
|
+
<br />
|
|
453
|
+
<SliderRange
|
|
454
|
+
min={0}
|
|
455
|
+
max={100}
|
|
456
|
+
size={EComponentSize.MD}
|
|
457
|
+
marks={MARKS}
|
|
458
|
+
values={values}
|
|
459
|
+
onChange={setValues}
|
|
460
|
+
reverse
|
|
461
|
+
/>
|
|
462
|
+
</div>
|
|
463
|
+
);
|
|
464
|
+
};
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
### Amounts
|
|
468
|
+
|
|
469
|
+
`stories/Slider/examples/SliderRange/Amounts.tsx`
|
|
470
|
+
|
|
471
|
+
```tsx
|
|
472
|
+
import React, { useState } from "react";
|
|
473
|
+
import { EComponentSize, SliderRange, TSliderRangeValues } from "@sberbusiness/triplex-next";
|
|
474
|
+
|
|
475
|
+
/** Возвращает значения от from до to (не включая), с шагом step. */
|
|
476
|
+
const range = (from: number, to: number, step: number): number[] => {
|
|
477
|
+
const values: number[] = [];
|
|
478
|
+
|
|
479
|
+
for (let value = from; value < to; value += step) {
|
|
480
|
+
values.push(value);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
return values;
|
|
484
|
+
};
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* Шкала сумм с неравномерным шагом: чем больше сумма, тем крупнее шаг.
|
|
488
|
+
* Слайдер работает с индексами шкалы, поэтому шаги равны визуально.
|
|
489
|
+
*/
|
|
490
|
+
const AMOUNTS = [
|
|
491
|
+
0,
|
|
492
|
+
10000,
|
|
493
|
+
...range(20000, 240000, 20000),
|
|
494
|
+
...range(240000, 1000000, 40000),
|
|
495
|
+
...range(1000000, 2150000, 50000),
|
|
496
|
+
...range(2150000, 2600000, 75000),
|
|
497
|
+
...range(2600000, 3000000, 100000),
|
|
498
|
+
...range(3000000, 3800000, 200000),
|
|
499
|
+
...range(3800000, 9000000, 400000),
|
|
500
|
+
...range(9000000, 10000000, 500000),
|
|
501
|
+
...range(10000000, 25000000, 2500000),
|
|
502
|
+
...range(25000000, 40000000, 5000000),
|
|
503
|
+
...range(40000000, 90000001, 10000000),
|
|
504
|
+
100000000,
|
|
505
|
+
];
|
|
506
|
+
|
|
507
|
+
const amountFormat = new Intl.NumberFormat("ru-RU");
|
|
508
|
+
|
|
509
|
+
const MARKS = [
|
|
510
|
+
{ value: 0, label: "0 млн" },
|
|
511
|
+
{ value: AMOUNTS.indexOf(1000000), label: "1 млн" },
|
|
512
|
+
{ value: AMOUNTS.indexOf(3000000), label: "3 млн" },
|
|
513
|
+
{ value: AMOUNTS.length - 1, label: "100 млн" },
|
|
514
|
+
];
|
|
515
|
+
|
|
516
|
+
export const Amounts = () => {
|
|
517
|
+
const [amountIndexes, setAmountIndexes] = useState<TSliderRangeValues>([
|
|
518
|
+
AMOUNTS.indexOf(1000000),
|
|
519
|
+
AMOUNTS.indexOf(3000000),
|
|
520
|
+
]);
|
|
521
|
+
|
|
522
|
+
return (
|
|
523
|
+
<div style={{ maxWidth: "750px", padding: "30px" }}>
|
|
524
|
+
<div>
|
|
525
|
+
от {amountFormat.format(AMOUNTS[amountIndexes[0]])} до {amountFormat.format(AMOUNTS[amountIndexes[1]])}
|
|
526
|
+
</div>
|
|
527
|
+
<br />
|
|
528
|
+
<SliderRange
|
|
529
|
+
min={0}
|
|
530
|
+
max={AMOUNTS.length - 1}
|
|
531
|
+
size={EComponentSize.MD}
|
|
532
|
+
marks={MARKS}
|
|
533
|
+
values={amountIndexes}
|
|
534
|
+
onChange={setAmountIndexes}
|
|
535
|
+
renderTooltipContent={(value) => amountFormat.format(AMOUNTS[value])}
|
|
536
|
+
/>
|
|
537
|
+
</div>
|
|
538
|
+
);
|
|
539
|
+
};
|
|
540
|
+
```
|
|
157
541
|
|
|
158
|
-
| Дата | Изменение |
|
|
159
|
-
|---|---|
|
|
160
|
-
| 2026-04-18 | Создан документ |
|