@sberbusiness/triplex-next-mcp-server 1.50.0-beta → 1.53.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/bundle/components/AlertProcess.md +6 -3
- package/bundle/components/AlertProcessSpoiler.md +216 -0
- package/bundle/components/Badge.md +4 -3
- package/bundle/components/ButtonDropdown.md +5 -3
- package/bundle/components/Carousel.md +671 -0
- package/bundle/components/CarouselExtended.md +7 -5
- package/bundle/components/CarouselIndicators.md +666 -0
- package/bundle/components/CarouselItem.md +579 -0
- package/bundle/components/CarouselNextButton.md +633 -0
- package/bundle/components/CarouselPrevButton.md +633 -0
- package/bundle/components/CarouselViewport.md +631 -0
- package/bundle/components/CheckboxTreeExtended.md +21 -7
- package/bundle/components/CheckboxTreeExtendedCheckbox.md +591 -0
- package/bundle/components/CheckboxTreeExtendedNode.md +577 -0
- package/bundle/components/Chip.md +1 -0
- package/bundle/components/ChipDatePicker.md +332 -0
- package/bundle/components/ChipDatePickerTarget.md +316 -0
- package/bundle/components/ChipMonthYearPicker.md +4 -3
- package/bundle/components/ChipMonthYearPickerTarget.md +318 -0
- package/bundle/components/ChipMultiselect.md +63 -73
- package/bundle/components/ChipSelect.md +385 -0
- package/bundle/components/ChipSelectTarget.md +388 -0
- package/bundle/components/ChipSuggest.md +10 -16
- package/bundle/components/ChipSuggestTarget.md +506 -0
- package/bundle/components/CollapsibleTree.md +2 -1
- package/bundle/components/CollapsibleTreeExtended.md +2 -1
- package/bundle/components/DateField.md +4 -2
- package/bundle/components/DatePickerExtended.md +16 -14
- package/bundle/components/DateRange.md +17 -10
- package/bundle/components/Dropdown.md +36 -63
- package/bundle/components/EmptyView.md +9 -4
- package/bundle/components/ExpandAnimation.md +9 -5
- package/bundle/components/FormField.md +1 -1
- package/bundle/components/ImageGallery.md +19 -52
- package/bundle/components/ImageGalleryExtended.md +123 -139
- package/bundle/components/ListItemControlsButtonDropdown.md +3 -2
- package/bundle/components/LoaderSmall.md +5 -4
- package/bundle/components/MonthYearRange.md +334 -0
- package/bundle/components/MultiselectField.md +81 -143
- package/bundle/components/Notification.md +2 -1
- package/bundle/components/NotificationGrouped.md +174 -0
- package/bundle/components/NumberField.md +259 -116
- package/bundle/components/OrderedList.md +149 -112
- package/bundle/components/Overlay.md +3 -2
- package/bundle/components/PageIndicators.md +306 -0
- package/bundle/components/Pagination.md +81 -3
- package/bundle/components/PaginationExtended.md +160 -0
- package/bundle/components/Portal.md +5 -2
- package/bundle/components/Puller.md +375 -0
- package/bundle/components/SMSField.md +277 -102
- package/bundle/components/SelectExtendedField.md +11 -7
- package/bundle/components/SelectField.md +7 -4
- package/bundle/components/SmartSpot.md +259 -0
- package/bundle/components/Step.md +2 -1
- package/bundle/components/Stepper.md +7 -7
- package/bundle/components/Suggest.md +7 -6
- package/bundle/components/SuggestField.md +8 -6
- package/bundle/components/TableBasic.md +12 -14
- package/bundle/components/Tooltip.md +9 -8
- package/bundle/guides/commits.md +4 -1
- package/bundle/guides/context.md +5 -5
- package/bundle/guides/stories.md +29 -21
- package/bundle/manifest.json +145 -13
- package/bundle/release-notes/1.50.0.mdx +4 -0
- package/bundle/release-notes/1.51.0.mdx +57 -0
- package/bundle/release-notes/1.52.0.mdx +55 -0
- package/bundle/release-notes/1.53.0.mdx +60 -0
- package/bundle/release-notes/1.54.0.mdx +45 -0
- package/bundle/release-notes/1.55.0.mdx +7 -0
- package/bundle/tokens.json +377 -222
- package/dist/index.js +39 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,631 @@
|
|
|
1
|
+
---
|
|
2
|
+
component: CarouselViewport
|
|
3
|
+
category: Carousel
|
|
4
|
+
related: [Carousel]
|
|
5
|
+
tokens: []
|
|
6
|
+
stories: stories/Carousel/Carousel.stories.tsx
|
|
7
|
+
version: "1.0"
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# CarouselViewport
|
|
11
|
+
|
|
12
|
+
## Назначение
|
|
13
|
+
|
|
14
|
+
Область просмотра составного `Carousel`: обрезает ленту по своим границам,
|
|
15
|
+
применяет отступы родителя и обрабатывает touch-жесты. Передаёт свой DOM-узел
|
|
16
|
+
в контекст, чтобы `Carousel` измерял доступный размер и рассчитывал прокрутку.
|
|
17
|
+
|
|
18
|
+
Используй внутри `Carousel`, оборачивая одну `Carousel.Track` со слайдами.
|
|
19
|
+
Самостоятельной прокрутки и геометрии вне родителя нет; для свободно
|
|
20
|
+
прокручиваемой ленты рассмотри `CarouselExtended`.
|
|
21
|
+
|
|
22
|
+
Публичное обращение — `Carousel.Viewport` через
|
|
23
|
+
`import { Carousel } from "@sberbusiness/triplex-next"`.
|
|
24
|
+
Именованного экспорта `CarouselViewport` из пакета нет;
|
|
25
|
+
`ICarouselViewportProps` экспортируется из публичного barrel семейства.
|
|
26
|
+
|
|
27
|
+
```tsx
|
|
28
|
+
import React from "react";
|
|
29
|
+
import { Carousel } from "@sberbusiness/triplex-next";
|
|
30
|
+
|
|
31
|
+
export const CardsCarousel = () => (
|
|
32
|
+
<Carousel viewportPadding={[8, 16]} style={{ width: 400, height: 180 }}>
|
|
33
|
+
<Carousel.Viewport>
|
|
34
|
+
<Carousel.Track>
|
|
35
|
+
{["Первая карточка", "Вторая карточка", "Третья карточка"].map((text, index) => (
|
|
36
|
+
<Carousel.Item key={text} index={index} style={{ width: 240, height: 164 }}>
|
|
37
|
+
{text}
|
|
38
|
+
</Carousel.Item>
|
|
39
|
+
))}
|
|
40
|
+
</Carousel.Track>
|
|
41
|
+
<Carousel.PrevButton aria-label="Предыдущая карточка" />
|
|
42
|
+
<Carousel.NextButton aria-label="Следующая карточка" />
|
|
43
|
+
</Carousel.Viewport>
|
|
44
|
+
</Carousel>
|
|
45
|
+
);
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Кнопки навигации внутри области появляются при наведении на неё.
|
|
49
|
+
Индикаторы страниц размещай рядом с `Viewport`, чтобы они не попадали под
|
|
50
|
+
`overflow: hidden`.
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Варианты и props
|
|
55
|
+
|
|
56
|
+
Обязательных props и собственных вариантов оформления нет.
|
|
57
|
+
`ICarouselViewportProps` наследует HTML-атрибуты `<div>`.
|
|
58
|
+
|
|
59
|
+
| Prop | Назначение и ограничения |
|
|
60
|
+
|---|---|
|
|
61
|
+
| `children` | Лента `Carousel.Track` со слайдами и необязательные кнопки навигации |
|
|
62
|
+
| `className` | Дополняет внутренний класс `viewPort` |
|
|
63
|
+
| `style` | Передаётся на корневой `<div>`; приоритет runtime-переменной — см. «Дизайн-токены» |
|
|
64
|
+
| `onTouchStart` | Вызывается после начала жеста, поэтому `preventDefault` в callback его не отменяет. При пустом `targetTouches` жест не начинается |
|
|
65
|
+
| `onTouchMove`, `onTouchEnd`, `onTouchCancel` и остальные HTML-обработчики | Передаются на `<div>`. Встроенные move/end/cancel слушают `window` |
|
|
66
|
+
| `aria-*`, `data-*`, `role`, `tabIndex` и остальные HTML-атрибуты | Передаются на корневой элемент; семантику и доступное имя задаёт потребитель |
|
|
67
|
+
| `ref` | Внешний callback- или object-ref объединяется с `viewportRef` контекста и указывает на тот же `HTMLDivElement` |
|
|
68
|
+
|
|
69
|
+
`orientation`, `viewportPadding` и `scrollMode` задаются на родительском
|
|
70
|
+
`Carousel`, а не на `Viewport`. Не подменяй `viewportPadding` через CSS:
|
|
71
|
+
измерения родителя должны соответствовать оформлению области.
|
|
72
|
+
|
|
73
|
+
Корневой `<div>` имеет `width: 100%`, `height: 100%`, `box-sizing: border-box`
|
|
74
|
+
и `overflow: hidden`. Ограничь размер композиции; для вертикальной карусели
|
|
75
|
+
нужна ограниченная высота.
|
|
76
|
+
|
|
77
|
+
### Touch-жесты
|
|
78
|
+
|
|
79
|
+
- Учитывается первое касание из `targetTouches`; `touchmove` — с
|
|
80
|
+
`{ passive: false }`, пока жест не отдан прокрутке страницы.
|
|
81
|
+
- Ось выбирается, когда смещение по любой оси стало **больше 5 px**
|
|
82
|
+
(`AXIS_LOCK_THRESHOLD`). Если поперечное смещение больше основного, жест
|
|
83
|
+
до конца отдаётся прокрутке страницы: без `preventDefault`, сдвига и
|
|
84
|
+
навигации. При равенстве выигрывает ось карусели. До выбора оси лента
|
|
85
|
+
не сдвигается, а `touchmove` не отменяется, чтобы браузер мог начать
|
|
86
|
+
прокрутку страницы. CSS-переход ленты отключается только после выбора
|
|
87
|
+
оси карусели, поэтому тап или прокрутка страницы не обрывают анимацию
|
|
88
|
+
перехода.
|
|
89
|
+
- За границей ленты смещение уменьшается втрое (rubber-band,
|
|
90
|
+
`RUBBER_BAND_RESISTANCE`).
|
|
91
|
+
- На `touchend` смещение вдоль оси **больше 50 px** (`SWIPE_THRESHOLD`)
|
|
92
|
+
вызывает `prevSlide`, меньше `-50 px` — `nextSlide`; ровно ±50 px не
|
|
93
|
+
переключает. Шаг перехода определяет родитель по своему состоянию на момент
|
|
94
|
+
`touchend`. `touchcancel` навигацию не вызывает.
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## Дизайн-токены
|
|
99
|
+
|
|
100
|
+
У `Viewport` нет собственных цветовых дизайн-токенов.
|
|
101
|
+
|
|
102
|
+
`--triplex-next-runtime-carousel-viewport-padding` — внутренняя runtime-переменная
|
|
103
|
+
с нормализованным `viewportPadding` родителя. Это не дизайн-токен, её не нужно
|
|
104
|
+
добавлять в `ThemeProvider` или генерируемые файлы тем. Значение из контекста
|
|
105
|
+
имеет приоритет над одноимённым свойством в `style` потребителя.
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## Инварианты
|
|
110
|
+
|
|
111
|
+
- Сохраняются `forwardRef`, корневой `<div>`, ref-target `HTMLDivElement`,
|
|
112
|
+
`displayName="Carousel.Viewport"`, класс `viewPort` и публичный
|
|
113
|
+
`ICarouselViewportProps`. Объединение внешнего ref с ref контекста обязательно
|
|
114
|
+
для измерений родителя; при размонтировании оба получают `null`.
|
|
115
|
+
- Контекст содержит один ref области и один ref ленты; несколько областей
|
|
116
|
+
в одном родителе не поддерживаются.
|
|
117
|
+
- Имя runtime-переменной padding и существующие barrel-экспорты сохраняются.
|
|
118
|
+
- Код остаётся совместимым с React 17.
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## Accessibility
|
|
123
|
+
|
|
124
|
+
- Область — обычный `<div>` без встроенной роли, `tabIndex`, ARIA-текста
|
|
125
|
+
или клавиатурной обработки. Для имени композиции при необходимости задай
|
|
126
|
+
`role="region"` и `aria-label` родителю; язык выбирает потребитель.
|
|
127
|
+
- Горизонтальная область использует `touch-action: pan-y`, вертикальная —
|
|
128
|
+
`pan-x`.
|
|
129
|
+
- Клавиатурная навигация — через [CarouselPrevButton](CarouselPrevButton-ai.md) /
|
|
130
|
+
[CarouselNextButton](CarouselNextButton-ai.md) и, в `PAGE`,
|
|
131
|
+
[CarouselIndicators](CarouselIndicators-ai.md).
|
|
132
|
+
- `overflow: hidden` не исключает невидимые слайды из accessibility-дерева
|
|
133
|
+
и Tab-порядка. `aria-hidden` / `inert`, связи слайдов с индикаторами,
|
|
134
|
+
управление фокусом и объявления смены слайда автоматически не создаются.
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Связанные компоненты
|
|
139
|
+
|
|
140
|
+
- [Carousel](Carousel-ai.md) — родитель, владелец геометрии, состояния и
|
|
141
|
+
навигации; публично предоставляет область как `.Viewport`.
|
|
142
|
+
- `Carousel.Track` — вложенный `<div role="presentation">`, располагает
|
|
143
|
+
слайды и применяет постоянное смещение из контекста. Во время жеста
|
|
144
|
+
`Viewport` временно управляет его inline `transform` / `transition`.
|
|
145
|
+
- [CarouselItem](CarouselItem-ai.md) — слайд с последовательным `index`,
|
|
146
|
+
регистрируется для измерений родителя.
|
|
147
|
+
- [CarouselPrevButton](CarouselPrevButton-ai.md) /
|
|
148
|
+
[CarouselNextButton](CarouselNextButton-ai.md) — кнопки перехода.
|
|
149
|
+
- [CarouselIndicators](CarouselIndicators-ai.md) — навигация по страницам `PAGE`.
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## Stories
|
|
154
|
+
|
|
155
|
+
Основные истории: `stories/Carousel/Carousel.stories.tsx`
|
|
156
|
+
Файлы примеров: `stories/Carousel/examples/`
|
|
157
|
+
|
|
158
|
+
| Story | Example file | Что демонстрирует |
|
|
159
|
+
|---|---|---|
|
|
160
|
+
| `Playground` | `Playground.tsx` | Выбор ориентации и режима прокрутки родителя |
|
|
161
|
+
| `Default` | `Default.tsx` | Горизонтальная область с лентой и кнопками внутри |
|
|
162
|
+
| `ScrollModes` | `ScrollModes.tsx` | Область в `ITEM` и `PAGE`, индикаторы рядом с ней |
|
|
163
|
+
| `Orientations` | `Orientations.tsx` | Горизонтальная и вертикальная области с ограниченной высотой |
|
|
164
|
+
| `Production` | `Production.tsx` | Отступы области через `viewportPadding={8}` родителя |
|
|
165
|
+
| `DynamicIndicators` | `DynamicIndicators.tsx` | Постраничная лента с большим количеством элементов |
|
|
166
|
+
| `VisualTests` | `VisualTests.tsx` | Горизонтальная и вертикальная области с кнопками; снимаются только состояния кнопок |
|
|
167
|
+
|
|
168
|
+
Скриншот-покрытие stories описано в [Carousel](Carousel-ai.md#stories).
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## История изменений
|
|
173
|
+
|
|
174
|
+
| Дата | Изменение |
|
|
175
|
+
|---|---|
|
|
176
|
+
| 2026-10-21 | Создан документ. |
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## Примеры использования
|
|
181
|
+
|
|
182
|
+
### Default
|
|
183
|
+
|
|
184
|
+
`stories/Carousel/examples/Default.tsx`
|
|
185
|
+
|
|
186
|
+
```tsx
|
|
187
|
+
import React from "react";
|
|
188
|
+
import { Carousel } from "@sberbusiness/triplex-next";
|
|
189
|
+
|
|
190
|
+
const BASE_ITEM_STYLE: React.CSSProperties = {
|
|
191
|
+
display: "flex",
|
|
192
|
+
alignItems: "center",
|
|
193
|
+
justifyContent: "center",
|
|
194
|
+
boxSizing: "border-box",
|
|
195
|
+
borderRadius: "8px",
|
|
196
|
+
color: "#ffffff",
|
|
197
|
+
fontSize: "24px",
|
|
198
|
+
fontWeight: "bold",
|
|
199
|
+
userSelect: "none",
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
export const Default = () => {
|
|
203
|
+
const itemSizes = [92, 168, 220, 244, 252, 256, 252, 244, 220, 168, 92];
|
|
204
|
+
const items = itemSizes.map((size, index) => {
|
|
205
|
+
const totalItems = itemSizes.length;
|
|
206
|
+
const hue = (index / (totalItems - 1)) * 280;
|
|
207
|
+
|
|
208
|
+
return {
|
|
209
|
+
id: index + 1,
|
|
210
|
+
size,
|
|
211
|
+
backgroundColor: `hsl(${hue}, 50%, 50%)`,
|
|
212
|
+
};
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
const boxStyle: React.CSSProperties = {
|
|
216
|
+
width: "100%",
|
|
217
|
+
height: "256px",
|
|
218
|
+
maxWidth: "512px",
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
return (
|
|
222
|
+
<div style={boxStyle}>
|
|
223
|
+
<Carousel>
|
|
224
|
+
<Carousel.Viewport>
|
|
225
|
+
<Carousel.Track>
|
|
226
|
+
{items.map((item, index) => (
|
|
227
|
+
<Carousel.Item
|
|
228
|
+
key={item.id}
|
|
229
|
+
index={index}
|
|
230
|
+
style={{
|
|
231
|
+
...BASE_ITEM_STYLE,
|
|
232
|
+
width: item.size,
|
|
233
|
+
height: "100%",
|
|
234
|
+
backgroundColor: item.backgroundColor,
|
|
235
|
+
}}
|
|
236
|
+
>
|
|
237
|
+
{item.id}
|
|
238
|
+
</Carousel.Item>
|
|
239
|
+
))}
|
|
240
|
+
</Carousel.Track>
|
|
241
|
+
<Carousel.PrevButton aria-label="Previous slide" />
|
|
242
|
+
<Carousel.NextButton aria-label="Next slide" />
|
|
243
|
+
</Carousel.Viewport>
|
|
244
|
+
</Carousel>
|
|
245
|
+
</div>
|
|
246
|
+
);
|
|
247
|
+
};
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### ScrollModes
|
|
251
|
+
|
|
252
|
+
`stories/Carousel/examples/ScrollModes.tsx`
|
|
253
|
+
|
|
254
|
+
```tsx
|
|
255
|
+
import React from "react";
|
|
256
|
+
import { Carousel, ECarouselScrollMode } from "@sberbusiness/triplex-next";
|
|
257
|
+
|
|
258
|
+
interface ScrollModeItemProps {
|
|
259
|
+
scrollMode: ECarouselScrollMode;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const BASE_ITEM_STYLE: React.CSSProperties = {
|
|
263
|
+
display: "flex",
|
|
264
|
+
alignItems: "center",
|
|
265
|
+
justifyContent: "center",
|
|
266
|
+
boxSizing: "border-box",
|
|
267
|
+
height: "256px",
|
|
268
|
+
borderRadius: "8px",
|
|
269
|
+
color: "#ffffff",
|
|
270
|
+
fontSize: "24px",
|
|
271
|
+
fontWeight: "bold",
|
|
272
|
+
userSelect: "none",
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
const ScrollModeItem = ({ scrollMode }: ScrollModeItemProps) => {
|
|
276
|
+
const itemSizes = [92, 168, 220, 244, 252, 256, 252, 244, 220, 168, 92];
|
|
277
|
+
const items = itemSizes.map((size, index) => {
|
|
278
|
+
const totalItems = itemSizes.length;
|
|
279
|
+
const hue = (index / (totalItems - 1)) * 280;
|
|
280
|
+
|
|
281
|
+
return {
|
|
282
|
+
id: index + 1,
|
|
283
|
+
size,
|
|
284
|
+
backgroundColor: `hsl(${hue}, 50%, 50%)`,
|
|
285
|
+
};
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
const containerStyle: React.CSSProperties = {
|
|
289
|
+
maxWidth: "512px",
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
return (
|
|
293
|
+
<div style={containerStyle}>
|
|
294
|
+
<div style={{ marginBottom: 8, fontSize: 16, fontWeight: 700 }}>{scrollMode.toUpperCase()}</div>
|
|
295
|
+
<Carousel scrollMode={scrollMode}>
|
|
296
|
+
<Carousel.Viewport>
|
|
297
|
+
<Carousel.Track>
|
|
298
|
+
{items.map((item, index) => (
|
|
299
|
+
<Carousel.Item
|
|
300
|
+
key={item.id}
|
|
301
|
+
index={index}
|
|
302
|
+
style={{
|
|
303
|
+
...BASE_ITEM_STYLE,
|
|
304
|
+
width: item.size,
|
|
305
|
+
backgroundColor: item.backgroundColor,
|
|
306
|
+
}}
|
|
307
|
+
>
|
|
308
|
+
{item.id}
|
|
309
|
+
</Carousel.Item>
|
|
310
|
+
))}
|
|
311
|
+
</Carousel.Track>
|
|
312
|
+
<Carousel.PrevButton aria-label="Previous slide" />
|
|
313
|
+
<Carousel.NextButton aria-label="Next slide" />
|
|
314
|
+
</Carousel.Viewport>
|
|
315
|
+
<Carousel.Indicators />
|
|
316
|
+
</Carousel>
|
|
317
|
+
</div>
|
|
318
|
+
);
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
const SCROLL_MODES = Object.values(ECarouselScrollMode);
|
|
322
|
+
|
|
323
|
+
export const ScrollModes = () => (
|
|
324
|
+
<div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
|
|
325
|
+
{SCROLL_MODES.map((scrollMode) => (
|
|
326
|
+
<ScrollModeItem key={scrollMode} scrollMode={scrollMode} />
|
|
327
|
+
))}
|
|
328
|
+
</div>
|
|
329
|
+
);
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
### Orientations
|
|
333
|
+
|
|
334
|
+
`stories/Carousel/examples/Orientations.tsx`
|
|
335
|
+
|
|
336
|
+
```tsx
|
|
337
|
+
import React from "react";
|
|
338
|
+
import { Carousel, EOrientation } from "@sberbusiness/triplex-next";
|
|
339
|
+
|
|
340
|
+
interface OrientationItemProps {
|
|
341
|
+
orientation: EOrientation;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const BASE_ITEM_STYLE: React.CSSProperties = {
|
|
345
|
+
display: "flex",
|
|
346
|
+
alignItems: "center",
|
|
347
|
+
justifyContent: "center",
|
|
348
|
+
boxSizing: "border-box",
|
|
349
|
+
borderRadius: "8px",
|
|
350
|
+
color: "#ffffff",
|
|
351
|
+
fontSize: "24px",
|
|
352
|
+
fontWeight: "bold",
|
|
353
|
+
userSelect: "none",
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
const ORIENTATION_TO_ITEM_DIMENSIONS_MAP: Record<EOrientation, (size: number) => React.CSSProperties> = {
|
|
357
|
+
[EOrientation.HORIZONTAL]: (size) => ({
|
|
358
|
+
width: size,
|
|
359
|
+
height: "100%",
|
|
360
|
+
}),
|
|
361
|
+
[EOrientation.VERTICAL]: (size) => ({
|
|
362
|
+
width: "100%",
|
|
363
|
+
height: size / 2,
|
|
364
|
+
}),
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
const ORIENTATION_TO_GAP_MAP: Record<EOrientation, number> = {
|
|
368
|
+
[EOrientation.HORIZONTAL]: 16,
|
|
369
|
+
[EOrientation.VERTICAL]: 8,
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
const OrientationItem = ({ orientation }: OrientationItemProps) => {
|
|
373
|
+
const itemSizes = [92, 168, 220, 244, 252, 256, 252, 244, 220, 168, 92];
|
|
374
|
+
const items = itemSizes.map((size, index) => {
|
|
375
|
+
const totalItems = itemSizes.length;
|
|
376
|
+
const hue = (index / (totalItems - 1)) * 280;
|
|
377
|
+
|
|
378
|
+
return {
|
|
379
|
+
id: index + 1,
|
|
380
|
+
size,
|
|
381
|
+
backgroundColor: `hsl(${hue}, 50%, 50%)`,
|
|
382
|
+
};
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
const containerStyle: React.CSSProperties = {
|
|
386
|
+
maxWidth: "512px",
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
const viewportStyle: React.CSSProperties = {
|
|
390
|
+
height: "256px",
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
return (
|
|
394
|
+
<div style={containerStyle}>
|
|
395
|
+
<div style={{ marginBottom: 8, fontSize: 16, fontWeight: 700 }}>{orientation.toUpperCase()}</div>
|
|
396
|
+
<Carousel orientation={orientation} gap={ORIENTATION_TO_GAP_MAP[orientation]}>
|
|
397
|
+
<Carousel.Viewport style={viewportStyle}>
|
|
398
|
+
<Carousel.Track>
|
|
399
|
+
{items.map((item, index) => {
|
|
400
|
+
const itemStyle: React.CSSProperties = {
|
|
401
|
+
...BASE_ITEM_STYLE,
|
|
402
|
+
...ORIENTATION_TO_ITEM_DIMENSIONS_MAP[orientation](item.size),
|
|
403
|
+
backgroundColor: item.backgroundColor,
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
return (
|
|
407
|
+
<Carousel.Item key={item.id} index={index} style={itemStyle}>
|
|
408
|
+
{item.id}
|
|
409
|
+
</Carousel.Item>
|
|
410
|
+
);
|
|
411
|
+
})}
|
|
412
|
+
</Carousel.Track>
|
|
413
|
+
<Carousel.PrevButton aria-label="Previous slide" />
|
|
414
|
+
<Carousel.NextButton aria-label="Next slide" />
|
|
415
|
+
</Carousel.Viewport>
|
|
416
|
+
<Carousel.Indicators />
|
|
417
|
+
</Carousel>
|
|
418
|
+
</div>
|
|
419
|
+
);
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
const ORIENTATIONS = Object.values(EOrientation);
|
|
423
|
+
|
|
424
|
+
export const Orientations = () => (
|
|
425
|
+
<div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
|
|
426
|
+
{ORIENTATIONS.map((orientation) => (
|
|
427
|
+
<OrientationItem key={orientation} orientation={orientation} />
|
|
428
|
+
))}
|
|
429
|
+
</div>
|
|
430
|
+
);
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
### Production
|
|
434
|
+
|
|
435
|
+
`stories/Carousel/examples/Production.tsx`
|
|
436
|
+
|
|
437
|
+
```tsx
|
|
438
|
+
import React, { useState } from "react";
|
|
439
|
+
import {
|
|
440
|
+
Carousel,
|
|
441
|
+
CardAction,
|
|
442
|
+
ECardRoundingSize,
|
|
443
|
+
ECardTheme,
|
|
444
|
+
ECardContentPaddingSize,
|
|
445
|
+
Title,
|
|
446
|
+
ETitleSize,
|
|
447
|
+
EFontWeightTitle,
|
|
448
|
+
Text,
|
|
449
|
+
ETextSize,
|
|
450
|
+
EFontType,
|
|
451
|
+
Gap,
|
|
452
|
+
Link,
|
|
453
|
+
Button,
|
|
454
|
+
EButtonTheme,
|
|
455
|
+
EComponentSize,
|
|
456
|
+
} from "@sberbusiness/triplex-next";
|
|
457
|
+
import { DefaulticonStrokePrdIcon20 } from "@sberbusiness/icons-next";
|
|
458
|
+
|
|
459
|
+
const CARDS_IDS = Array.from({ length: 6 }, (_, i) => i);
|
|
460
|
+
|
|
461
|
+
const CardBody = () => (
|
|
462
|
+
<CardAction.Content.Body>
|
|
463
|
+
{[1, 2, 3].map((item) => (
|
|
464
|
+
<div key={item} style={{ display: "flex", alignItems: "center", marginTop: item > 1 ? "16px" : "0" }}>
|
|
465
|
+
<DefaulticonStrokePrdIcon20 paletteIndex={5} />
|
|
466
|
+
<Text size={ETextSize.B3} style={{ marginLeft: "8px" }}>
|
|
467
|
+
List item text
|
|
468
|
+
</Text>
|
|
469
|
+
</div>
|
|
470
|
+
))}
|
|
471
|
+
<Gap size={8} />
|
|
472
|
+
<Text tag="div" type={EFontType.SECONDARY} size={ETextSize.B4}>
|
|
473
|
+
This message provides additional context or highlights important information to note.
|
|
474
|
+
</Text>
|
|
475
|
+
<Gap size={8} />
|
|
476
|
+
<Text tag="div" type={EFontType.PRIMARY} size={ETextSize.B3}>
|
|
477
|
+
<Link onKeyDown={(event) => event.stopPropagation()} onClick={(event) => event.stopPropagation()}>
|
|
478
|
+
Link text
|
|
479
|
+
</Link>
|
|
480
|
+
</Text>
|
|
481
|
+
</CardAction.Content.Body>
|
|
482
|
+
);
|
|
483
|
+
|
|
484
|
+
export const Production = () => {
|
|
485
|
+
const [selectedId, setSelectedId] = useState<number | null>(null);
|
|
486
|
+
|
|
487
|
+
return (
|
|
488
|
+
<div style={{ width: "100%", maxWidth: "752px" }}>
|
|
489
|
+
<Carousel viewportPadding={8}>
|
|
490
|
+
<Carousel.Viewport>
|
|
491
|
+
<Carousel.Track>
|
|
492
|
+
{CARDS_IDS.map((id, index) => {
|
|
493
|
+
const selected = selectedId === id;
|
|
494
|
+
|
|
495
|
+
return (
|
|
496
|
+
<Carousel.Item
|
|
497
|
+
key={id}
|
|
498
|
+
index={index}
|
|
499
|
+
style={{
|
|
500
|
+
width: "216px",
|
|
501
|
+
height: "100%",
|
|
502
|
+
boxSizing: "border-box",
|
|
503
|
+
}}
|
|
504
|
+
>
|
|
505
|
+
<CardAction
|
|
506
|
+
roundingSize={ECardRoundingSize.MD}
|
|
507
|
+
theme={ECardTheme.GENERAL}
|
|
508
|
+
selected={selected}
|
|
509
|
+
toggle={() => setSelectedId(selected ? null : id)}
|
|
510
|
+
>
|
|
511
|
+
<CardAction.Media
|
|
512
|
+
style={{
|
|
513
|
+
backgroundImage: "url(assets/images/evotor.png)",
|
|
514
|
+
height: "129px",
|
|
515
|
+
}}
|
|
516
|
+
/>
|
|
517
|
+
<CardAction.Content paddingSize={ECardContentPaddingSize.SM}>
|
|
518
|
+
<CardAction.Content.Header>
|
|
519
|
+
<Title tag="div" size={ETitleSize.H3} weight={EFontWeightTitle.REGULAR}>
|
|
520
|
+
Title Text
|
|
521
|
+
</Title>
|
|
522
|
+
</CardAction.Content.Header>
|
|
523
|
+
|
|
524
|
+
<CardBody />
|
|
525
|
+
|
|
526
|
+
<CardAction.Content.Footer>
|
|
527
|
+
<Button
|
|
528
|
+
theme={
|
|
529
|
+
selected ? EButtonTheme.SECONDARY_LIGHT : EButtonTheme.SECONDARY
|
|
530
|
+
}
|
|
531
|
+
size={EComponentSize.SM}
|
|
532
|
+
onKeyDown={(event) => event.stopPropagation()}
|
|
533
|
+
onClick={(event) => event.stopPropagation()}
|
|
534
|
+
>
|
|
535
|
+
Button text
|
|
536
|
+
</Button>
|
|
537
|
+
</CardAction.Content.Footer>
|
|
538
|
+
</CardAction.Content>
|
|
539
|
+
</CardAction>
|
|
540
|
+
</Carousel.Item>
|
|
541
|
+
);
|
|
542
|
+
})}
|
|
543
|
+
</Carousel.Track>
|
|
544
|
+
<Carousel.PrevButton aria-label="Previous slide" />
|
|
545
|
+
<Carousel.NextButton aria-label="Next slide" />
|
|
546
|
+
</Carousel.Viewport>
|
|
547
|
+
</Carousel>
|
|
548
|
+
</div>
|
|
549
|
+
);
|
|
550
|
+
};
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
### DynamicIndicators
|
|
554
|
+
|
|
555
|
+
`stories/Carousel/examples/DynamicIndicators.tsx`
|
|
556
|
+
|
|
557
|
+
```tsx
|
|
558
|
+
import React from "react";
|
|
559
|
+
import { Carousel, ECarouselScrollMode } from "@sberbusiness/triplex-next";
|
|
560
|
+
|
|
561
|
+
const BASE_ITEM_STYLE: React.CSSProperties = {
|
|
562
|
+
display: "flex",
|
|
563
|
+
alignItems: "center",
|
|
564
|
+
justifyContent: "center",
|
|
565
|
+
boxSizing: "border-box",
|
|
566
|
+
borderRadius: "8px",
|
|
567
|
+
color: "#ffffff",
|
|
568
|
+
fontSize: "24px",
|
|
569
|
+
fontWeight: "bold",
|
|
570
|
+
userSelect: "none",
|
|
571
|
+
};
|
|
572
|
+
|
|
573
|
+
export const DynamicIndicators = () => {
|
|
574
|
+
const itemSizes = Array.from({ length: 20 }, () => 128);
|
|
575
|
+
|
|
576
|
+
const items = itemSizes.map((size, index) => {
|
|
577
|
+
const totalItems = itemSizes.length;
|
|
578
|
+
const hue = (index / (totalItems - 1)) * 280;
|
|
579
|
+
|
|
580
|
+
return {
|
|
581
|
+
id: index + 1,
|
|
582
|
+
size,
|
|
583
|
+
backgroundColor: `hsl(${hue}, 50%, 50%)`,
|
|
584
|
+
};
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
const boxStyle: React.CSSProperties = {
|
|
588
|
+
display: "flex",
|
|
589
|
+
flexDirection: "column",
|
|
590
|
+
gap: "16px",
|
|
591
|
+
width: "100%",
|
|
592
|
+
maxWidth: "512px",
|
|
593
|
+
};
|
|
594
|
+
|
|
595
|
+
const containerStyle: React.CSSProperties = {
|
|
596
|
+
width: "100%",
|
|
597
|
+
height: "256px",
|
|
598
|
+
};
|
|
599
|
+
|
|
600
|
+
return (
|
|
601
|
+
<div style={boxStyle}>
|
|
602
|
+
<div style={containerStyle}>
|
|
603
|
+
<Carousel scrollMode={ECarouselScrollMode.PAGE}>
|
|
604
|
+
<Carousel.Viewport>
|
|
605
|
+
<Carousel.Track>
|
|
606
|
+
{items.map((item, index) => (
|
|
607
|
+
<Carousel.Item
|
|
608
|
+
key={item.id}
|
|
609
|
+
index={index}
|
|
610
|
+
style={{
|
|
611
|
+
...BASE_ITEM_STYLE,
|
|
612
|
+
width: item.size,
|
|
613
|
+
height: "100%",
|
|
614
|
+
backgroundColor: item.backgroundColor,
|
|
615
|
+
}}
|
|
616
|
+
>
|
|
617
|
+
{item.id}
|
|
618
|
+
</Carousel.Item>
|
|
619
|
+
))}
|
|
620
|
+
</Carousel.Track>
|
|
621
|
+
<Carousel.PrevButton aria-label="Previous slide" />
|
|
622
|
+
<Carousel.NextButton aria-label="Next slide" />
|
|
623
|
+
</Carousel.Viewport>
|
|
624
|
+
<Carousel.Indicators />
|
|
625
|
+
</Carousel>
|
|
626
|
+
</div>
|
|
627
|
+
</div>
|
|
628
|
+
);
|
|
629
|
+
};
|
|
630
|
+
```
|
|
631
|
+
|