@sberbusiness/triplex-next-mcp-server 0.24.0-beta2 → 1.40.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 +10 -0
- package/bundle/components/AlertContext.md +268 -0
- package/bundle/components/BodyPage.md +186 -0
- package/bundle/components/Button.md +93 -85
- package/bundle/components/ButtonDropdown.md +36 -70
- package/bundle/components/ButtonDropdownExtended.md +422 -0
- package/bundle/components/ButtonIcon.md +41 -48
- package/bundle/components/Caption.md +291 -0
- package/bundle/components/Checkbox.md +43 -122
- package/bundle/components/CheckboxXGroup.md +109 -46
- package/bundle/components/CheckboxYGroup.md +77 -49
- package/bundle/components/CodeText.md +197 -0
- package/bundle/components/Col.md +311 -162
- package/bundle/components/CollapsibleTree.md +584 -0
- package/bundle/components/CollapsibleTreeExtended.md +422 -0
- package/bundle/components/Dropdown.md +748 -346
- package/bundle/components/EmptyView.md +14 -15
- package/bundle/components/FooterPage.md +203 -0
- package/bundle/components/Gap.md +115 -57
- package/bundle/components/HeaderPage.md +226 -0
- package/bundle/components/HelpBox.md +45 -84
- package/bundle/components/ImageGallery.md +423 -0
- package/bundle/components/ImageGalleryExtended.md +606 -0
- package/bundle/components/LightBox.md +2252 -233
- package/bundle/components/List.md +216 -183
- package/bundle/components/ListActionItem.md +157 -0
- package/bundle/components/ListItem.md +276 -0
- package/bundle/components/ListItemControlsButton.md +103 -0
- package/bundle/components/ListItemControlsButtonDropdown.md +107 -0
- package/bundle/components/ListItemSelectable.md +99 -0
- package/bundle/components/ListItemTable.md +390 -0
- package/bundle/components/ListMaster.md +876 -156
- package/bundle/components/ListMasterFooter.md +80 -0
- package/bundle/components/ListMasterHeader.md +86 -0
- package/bundle/components/ListSortable.md +153 -0
- package/bundle/components/ListSortableItem.md +175 -0
- package/bundle/components/ListTableItem.md +391 -0
- package/bundle/components/ModalWindow.md +427 -237
- package/bundle/components/MonthYearField.md +145 -109
- package/bundle/components/Notification.md +625 -155
- package/bundle/components/Overlay.md +271 -170
- package/bundle/components/Page.md +284 -186
- package/bundle/components/Pagination.md +206 -229
- package/bundle/components/Portal.md +131 -67
- package/bundle/components/Row.md +171 -69
- package/bundle/components/Text.md +320 -0
- package/bundle/components/TextField.md +364 -97
- package/bundle/components/Title.md +296 -0
- package/bundle/guides/codestyle.md +38 -11
- package/bundle/guides/commits.md +119 -26
- package/bundle/guides/context.md +34 -30
- package/bundle/guides/stories.md +110 -40
- package/bundle/guides/template.md +3 -7
- package/bundle/guides/tests.md +48 -2
- package/bundle/manifest.json +275 -30
- package/bundle/release-notes/1.26.0.mdx +33 -2
- package/bundle/release-notes/1.27.0.mdx +43 -0
- package/bundle/release-notes/1.28.0.mdx +25 -0
- package/bundle/release-notes/1.29.0.mdx +47 -0
- package/bundle/release-notes/1.30.0.mdx +56 -0
- package/bundle/release-notes/1.31.0.mdx +52 -0
- package/bundle/release-notes/1.32.0.mdx +35 -0
- package/bundle/release-notes/1.33.0.mdx +61 -0
- package/bundle/release-notes/1.34.0.mdx +35 -0
- package/bundle/release-notes/1.35.0.mdx +21 -0
- package/bundle/release-notes/1.35.1.mdx +11 -0
- package/bundle/release-notes/1.36.0.mdx +21 -0
- package/bundle/release-notes/1.37.0.mdx +71 -0
- package/bundle/release-notes/1.38.0.mdx +36 -0
- package/bundle/release-notes/1.39.0.mdx +66 -0
- package/bundle/release-notes/1.40.0.mdx +27 -0
- package/bundle/release-notes/1.41.0.mdx +7 -0
- package/package.json +1 -1
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
component: List
|
|
3
|
-
category:
|
|
4
|
-
|
|
5
|
-
figma-file: ""
|
|
6
|
-
related: [ListMaster, Table, TreeView, Checkbox, LoaderScreen]
|
|
3
|
+
category: List
|
|
4
|
+
related: [ListSortable, ListItem, ListEmptyState, ListItemLoading, LoaderScreen]
|
|
7
5
|
tokens: []
|
|
8
6
|
stories: stories/List/List.stories.tsx
|
|
9
7
|
version: "1.0"
|
|
@@ -13,235 +11,270 @@ version: "1.0"
|
|
|
13
11
|
|
|
14
12
|
## Назначение
|
|
15
13
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
на уровне списка.
|
|
14
|
+
Контейнер `<ul>` для набора элементов списка. Управляет состоянием загрузки
|
|
15
|
+
через prop `loading` (показывает overlay-спиннер `LoaderScreen` поверх
|
|
16
|
+
содержимого).
|
|
20
17
|
|
|
21
|
-
Используй когда: нужен
|
|
22
|
-
|
|
23
|
-
для древовидных — `TreeView`, для master-detail интерфейсов — `ListMaster`.
|
|
24
|
-
|
|
25
|
-
---
|
|
26
|
-
|
|
27
|
-
## Файловая структура
|
|
28
|
-
|
|
29
|
-
```text
|
|
30
|
-
src/components/List/
|
|
31
|
-
├── List.tsx # <ul>, loading-overlay
|
|
32
|
-
├── ListSortable.tsx # DnD обёртка (dnd-kit)
|
|
33
|
-
├── index.ts # Barrel всех слотов
|
|
34
|
-
├── components/
|
|
35
|
-
│ ├── ListItem.tsx # <li> + Provider ListItemContext
|
|
36
|
-
│ ├── ListItemContext.ts # { selected, setSelected }
|
|
37
|
-
│ ├── ListItemContent.tsx # Основной контент строки
|
|
38
|
-
│ ├── ListItemControls.tsx # Правый блок кнопок
|
|
39
|
-
│ ├── ListItemControlsButton.tsx # Стилизованная кнопка в Controls
|
|
40
|
-
│ ├── ListItemControlsButtonDropdown.tsx # Кнопка с выпадающим меню
|
|
41
|
-
│ ├── ListItemSelectable.tsx # Обёртка с Checkbox, пишет selected в Context
|
|
42
|
-
│ ├── ListItemTable.tsx # Табличный вид строки
|
|
43
|
-
│ ├── ListItemTailLeft.tsx # Левый хвост (иконка, аватар)
|
|
44
|
-
│ ├── ListItemTailRight.tsx # Правый хвост
|
|
45
|
-
│ ├── ListItemLoading.tsx # Placeholder-строка при loading
|
|
46
|
-
│ ├── ListEmptyState.tsx # Пустое состояние
|
|
47
|
-
│ ├── ListSortableItem.tsx # <li> с useSortable (DnD)
|
|
48
|
-
│ ├── ListSortableItemControls.tsx # DnD-handle control (drag)
|
|
49
|
-
│ └── ListSortableItemTarget.tsx # Цель сортировки
|
|
50
|
-
└── styles/*.module.less
|
|
51
|
-
```
|
|
18
|
+
Используй `List` когда: нужен стандартный список с произвольным контентом.
|
|
19
|
+
Используй `ListSortable` когда: нужна сортировка элементов через drag-and-drop.
|
|
52
20
|
|
|
53
21
|
---
|
|
54
22
|
|
|
55
23
|
## Варианты и props
|
|
56
24
|
|
|
57
|
-
###
|
|
25
|
+
### Опциональные props
|
|
58
26
|
|
|
59
|
-
| Prop | Тип |
|
|
27
|
+
| Prop | Тип | По умолчанию | Описание |
|
|
60
28
|
|---|---|---|---|
|
|
61
|
-
| `loading` | `boolean` |
|
|
62
|
-
|
|
|
29
|
+
| `loading` | `boolean` | `false` | Показывает `LoaderScreen` поверх содержимого. Используется при обновлении текущего списка новыми данными (например, после применения фильтра) |
|
|
30
|
+
| `children` | `React.ReactNode` | — | Элементы списка (обычно `<ListItem>` или `<ListTableItem>`) |
|
|
31
|
+
| `...HTMLUListElementAttributes` | — | — | Все стандартные атрибуты `<ul>` |
|
|
63
32
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
Создаёт собственный `ListItemContext` с `{ selected, setSelected }`. Подкомпоненты (`ListItemSelectable`) пишут в него.
|
|
67
|
-
|
|
68
|
-
### ListItemSelectable — `IListItemSelectableProps extends Omit<HTMLDivAttributes, "onSelect">`
|
|
69
|
-
|
|
70
|
-
| Prop | Тип | Обязателен | Описание |
|
|
71
|
-
|---|---|---|---|
|
|
72
|
-
| `selected` | `boolean` | Да | Управляемое состояние выбора. |
|
|
73
|
-
| `onSelect` | `(selected: boolean) => void` | Да | Callback при изменении. |
|
|
74
|
-
|
|
75
|
-
- Внутри — `<Checkbox>` + `useEffect(() => setSelected(selected))` — синхронизирует с `ListItemContext`, чтобы сиблинги (например, стили строки) могли реагировать на выбор.
|
|
76
|
-
- `<span className={styles.checkboxLabelClickArea} />` — растягивающаяся область клика (не только на чекбокс).
|
|
77
|
-
|
|
78
|
-
### ListItemContent — базовый слот для текста/описания строки.
|
|
79
|
-
|
|
80
|
-
### ListItemControls / ListItemControlsButton / …ButtonDropdown
|
|
33
|
+
---
|
|
81
34
|
|
|
82
|
-
|
|
83
|
-
|---|---|
|
|
84
|
-
| `ListItemControls` | Контейнер для кнопок справа. |
|
|
85
|
-
| `ListItemControlsButton` | Стилизованная кнопка-иконка. |
|
|
86
|
-
| `ListItemControlsButtonDropdown` | Кнопка + вложенное выпадающее меню. |
|
|
35
|
+
## Инварианты
|
|
87
36
|
|
|
88
|
-
|
|
37
|
+
- **`forwardRef`** — обязателен, target — `HTMLUListElement`. Не убирать.
|
|
38
|
+
- **Корневой элемент `<ul>`** — не менять. Семантика списка важна для accessibility.
|
|
39
|
+
- `loading` — overlay-стратегия (содержимое не размонтируется), не путать с `ListItemLoading` (отдельный элемент-спиннер для пагинации).
|
|
40
|
+
- **`.listLoaderScreen` — `z-index: @z-index-step`**. Понижает z-index относительно глобального `@z-index-loader-screen` у `LoaderScreen`, чтобы спиннер перекрывал только содержимое списка, а не `LightBox` / `ModalWindow`. Не повышать без проверки наложения на модальные слои.
|
|
89
41
|
|
|
90
|
-
|
|
42
|
+
---
|
|
91
43
|
|
|
92
|
-
|
|
44
|
+
## Связанные компоненты
|
|
93
45
|
|
|
94
|
-
|
|
46
|
+
- `ListItem` — базовый элемент списка, провайдер контекста для `selected`-состояния.
|
|
47
|
+
- `ListSortable` — тот же `<ul>`, но с DnD-обвязкой через `@dnd-kit`.
|
|
48
|
+
- `ListEmptyState` — отображается вместо элементов, когда данных нет (например, после фильтра).
|
|
49
|
+
- `ListItemLoading` — элемент-спиннер для подгрузки следующей страницы (пагинация).
|
|
50
|
+
- `LoaderScreen` — используется внутри `List` для состояния `loading`.
|
|
95
51
|
|
|
96
|
-
|
|
52
|
+
---
|
|
97
53
|
|
|
98
|
-
|
|
54
|
+
## Stories
|
|
99
55
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
| `items` | `T[]` | Да | Массив элементов, у каждого должно быть поле `id`. |
|
|
103
|
-
| `onItemsChange` | `(items: T[]) => void` | Да | Callback после перестановки. |
|
|
104
|
-
| `...IListProps` | — | — | Проброс на `<List>`. |
|
|
56
|
+
Основные истории: `stories/List/List.stories.tsx`
|
|
57
|
+
Файлы примеров: `stories/List/examples/List/`
|
|
105
58
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
59
|
+
| Story | Example file | Что демонстрирует |
|
|
60
|
+
|---|---|---|
|
|
61
|
+
| `Playground` | `Playground.tsx` | Интерактивный контроль `loading` |
|
|
62
|
+
| `Default` | `Default.tsx` | Базовый список |
|
|
63
|
+
| `Loading` | `Loading.tsx` | Состояние загрузки (overlay) |
|
|
64
|
+
| `EmptyState` | `EmptyState.tsx` | Использование `ListEmptyState` |
|
|
65
|
+
| `Virtualized` | `Virtualized.tsx` | Виртуализированный список (большие наборы данных) |
|
|
66
|
+
| `Sortable` | `Sortable.tsx` | DnD-сортировка через `ListSortable` + `ListSortableItem` |
|
|
67
|
+
| `WithActionItems` | `WithActionItems.tsx` | Список из кликабельных строк `ListActionItem` |
|
|
68
|
+
| `SortableWithInteractiveElements` | `SortableWithInteractiveElements.tsx` | DnD с интерактивными элементами внутри строк (`data-draggable="false"`) |
|
|
109
69
|
|
|
110
|
-
|
|
70
|
+
---
|
|
111
71
|
|
|
112
|
-
|
|
113
|
-
|---|---|---|---|
|
|
114
|
-
| `id` | `string` | Да | Уникальный идентификатор, должен совпадать с `items[i].id`. |
|
|
115
|
-
| `disabled` | `boolean` | Нет | Блокирует drag. |
|
|
116
|
-
| `children` | `ReactNode \| (provideProps) => ReactNode` | — | **Render-props или обычные children**. Provide-props: `{ dragging, disabled, listeners, setActivatorNodeRef }`. |
|
|
72
|
+
## История изменений
|
|
117
73
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
-
|
|
74
|
+
| Дата | Изменение |
|
|
75
|
+
|---|---|
|
|
76
|
+
| 2026-04-29 | Создан документ |
|
|
77
|
+
| 2026-06-17 | `List.module.less`: локальный `z-index: @z-index-step` на `.listLoaderScreen` — исправлено перекрытие `LoaderScreen` поверх `LightBox` / `ModalWindow` |
|
|
121
78
|
|
|
122
79
|
---
|
|
123
80
|
|
|
124
|
-
##
|
|
81
|
+
## Примеры использования
|
|
125
82
|
|
|
126
|
-
###
|
|
127
|
-
|
|
128
|
-
```ts
|
|
129
|
-
while (element !== null) {
|
|
130
|
-
if (element.dataset?.draggable === "false") return false;
|
|
131
|
-
element = element.parentElement;
|
|
132
|
-
}
|
|
133
|
-
```
|
|
83
|
+
### Default
|
|
134
84
|
|
|
135
|
-
|
|
136
|
-
- `MouseSensor`: игнорирует правую кнопку (`button === 2`).
|
|
137
|
-
- `TouchSensor`: игнорирует мультитач (`touches.length > 1`).
|
|
138
|
-
|
|
139
|
-
### `ListItemContext` — связь выбора и стилей сиблингов
|
|
85
|
+
`stories/List/examples/List/Default.tsx`
|
|
140
86
|
|
|
141
87
|
```tsx
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
88
|
+
import React from "react";
|
|
89
|
+
import { List, ListItem } from "@sberbusiness/triplex-next";
|
|
90
|
+
|
|
91
|
+
export const Default = () => (
|
|
92
|
+
<div style={{ width: "500px" }}>
|
|
93
|
+
<List>
|
|
94
|
+
<ListItem>Элемент списка</ListItem>
|
|
95
|
+
<ListItem>Элемент списка</ListItem>
|
|
96
|
+
<ListItem>Элемент списка</ListItem>
|
|
97
|
+
</List>
|
|
98
|
+
</div>
|
|
99
|
+
);
|
|
145
100
|
```
|
|
146
101
|
|
|
147
|
-
|
|
148
|
-
- Другие подкомпоненты (например, `ListItemContent`) могут подписаться и изменить стиль.
|
|
149
|
-
- **Паттерн "выбор известен всей строке"** — без prop-drilling.
|
|
102
|
+
### Loading
|
|
150
103
|
|
|
151
|
-
|
|
104
|
+
`stories/List/examples/List/Loading.tsx`
|
|
152
105
|
|
|
153
106
|
```tsx
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
107
|
+
import React from "react";
|
|
108
|
+
import { List, ListItem } from "@sberbusiness/triplex-next";
|
|
109
|
+
|
|
110
|
+
export const Loading = () => (
|
|
111
|
+
<div style={{ width: "150px" }}>
|
|
112
|
+
<List loading>
|
|
113
|
+
<ListItem>Элемент списка</ListItem>
|
|
114
|
+
<ListItem>Элемент списка</ListItem>
|
|
115
|
+
<ListItem>Элемент списка</ListItem>
|
|
116
|
+
<ListItem>Элемент списка</ListItem>
|
|
117
|
+
<ListItem>Элемент списка</ListItem>
|
|
118
|
+
<ListItem>Элемент списка</ListItem>
|
|
119
|
+
</List>
|
|
120
|
+
</div>
|
|
121
|
+
);
|
|
158
122
|
```
|
|
159
123
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
### `data-tx={process.env.npm_package_version}` на `<ul>` и `<li>`
|
|
163
|
-
|
|
164
|
-
Версия библиотеки в DOM для отладки. Сохранять при изменениях.
|
|
124
|
+
### EmptyState
|
|
165
125
|
|
|
166
|
-
|
|
126
|
+
`stories/List/examples/List/EmptyState.tsx`
|
|
167
127
|
|
|
168
|
-
```
|
|
169
|
-
|
|
128
|
+
```tsx
|
|
129
|
+
import React from "react";
|
|
130
|
+
import {
|
|
131
|
+
Button,
|
|
132
|
+
EButtonTheme,
|
|
133
|
+
EComponentSize,
|
|
134
|
+
EFontWeightTitle,
|
|
135
|
+
ETextSize,
|
|
136
|
+
ETitleSize,
|
|
137
|
+
Gap,
|
|
138
|
+
ListEmptyState,
|
|
139
|
+
Text,
|
|
140
|
+
Title,
|
|
141
|
+
} from "@sberbusiness/triplex-next";
|
|
142
|
+
import { EmptytableSysIcon96 } from "@sberbusiness/icons-next";
|
|
143
|
+
|
|
144
|
+
export const EmptyState = () => (
|
|
145
|
+
<ListEmptyState>
|
|
146
|
+
<div style={{ display: "flex", justifyContent: "center" }}>
|
|
147
|
+
<EmptytableSysIcon96 />
|
|
148
|
+
</div>
|
|
149
|
+
|
|
150
|
+
<Gap size={16} />
|
|
151
|
+
|
|
152
|
+
<Title size={ETitleSize.H3} weight={EFontWeightTitle.REGULAR}>
|
|
153
|
+
Title text
|
|
154
|
+
</Title>
|
|
155
|
+
|
|
156
|
+
<Gap size={8} />
|
|
157
|
+
|
|
158
|
+
<Text size={ETextSize.B3}>Нет данных, но можно предложить какие-то действия для заполнения таблицы.</Text>
|
|
159
|
+
|
|
160
|
+
<Gap size={24} />
|
|
161
|
+
|
|
162
|
+
<Button theme={EButtonTheme.SECONDARY} size={EComponentSize.SM}>
|
|
163
|
+
Button Name
|
|
164
|
+
</Button>
|
|
165
|
+
<Button theme={EButtonTheme.GENERAL} size={EComponentSize.SM}>
|
|
166
|
+
Button Name
|
|
167
|
+
</Button>
|
|
168
|
+
</ListEmptyState>
|
|
169
|
+
);
|
|
170
170
|
```
|
|
171
171
|
|
|
172
|
-
|
|
172
|
+
### Virtualized
|
|
173
173
|
|
|
174
|
-
|
|
174
|
+
`stories/List/examples/List/Virtualized.tsx`
|
|
175
175
|
|
|
176
176
|
```tsx
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
177
|
+
import React from "react";
|
|
178
|
+
import { FixedSizeList } from "react-window";
|
|
179
|
+
import { List, ListItem } from "@sberbusiness/triplex-next";
|
|
180
|
+
|
|
181
|
+
const itemData = Array.from({ length: 100 }).map((_, index) => `List item ${index}`);
|
|
182
|
+
|
|
183
|
+
export const Virtualized = () => (
|
|
184
|
+
<FixedSizeList
|
|
185
|
+
itemData={itemData}
|
|
186
|
+
itemCount={itemData.length}
|
|
187
|
+
itemSize={20}
|
|
188
|
+
width="100%"
|
|
189
|
+
height={200}
|
|
190
|
+
innerElementType={List}
|
|
191
|
+
>
|
|
192
|
+
{({ data, index, style }) => <ListItem style={style}>{data[index]}</ListItem>}
|
|
193
|
+
</FixedSizeList>
|
|
194
|
+
);
|
|
187
195
|
```
|
|
188
196
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
---
|
|
192
|
-
|
|
193
|
-
## Инварианты
|
|
194
|
-
|
|
195
|
-
- **`<ul>` + `<li>`** семантика — не заменять на `<div>`.
|
|
196
|
-
- **`ListItemContext` shape** (`selected`, `setSelected`) — публичная точка расширения для сторонних кастомных подкомпонентов.
|
|
197
|
-
- **`data-draggable="false"`** — атрибут-escape-hatch для ListSortable. Потребители в product-коде могут опираться на него, не менять семантику.
|
|
198
|
-
- **`ListSortable` требует `items[].id: string`** — id как в React key, так и в dnd-kit. Переход на number/symbol — breaking.
|
|
199
|
-
- **`arrayMove` от dnd-kit** — не собственная реализация. При смене DnD-библиотеки переписать.
|
|
200
|
-
- **`IListSortableItemProps.children` — `ReactNode` или функция** — публичный render-props API.
|
|
201
|
-
- **`LoaderScreen` внутри `<ul>`** — визуальное решение, не менять без согласования.
|
|
202
|
-
- **ForwardRef** на всех компонентах; `ListSortable` — generic через `as <T>(props) => ...` cast.
|
|
203
|
-
- **`Object.assign` на `ListSortableItem` → `Target`** — сохранить.
|
|
204
|
-
|
|
205
|
-
---
|
|
206
|
-
|
|
207
|
-
## Accessibility
|
|
208
|
-
|
|
209
|
-
- Семантика списка `<ul>`/`<li>` — правильная. Screen reader'ы объявляют "list, N items".
|
|
210
|
-
- `ListItemSelectable` — `<Checkbox>` внутри: нативная клавиатурная навигация, label через расширяющую clickable-область.
|
|
211
|
-
- `ListItemControlsButton` / `ListItemControlsButtonDropdown` — проверь, что `aria-label` проставлен (иконки без текста).
|
|
212
|
-
- **DnD не доступен с клавиатуры** из коробки (dnd-kit поддерживает `KeyboardSensor`, но здесь не подключён). Known debt.
|
|
213
|
-
- Loader'овый overlay скрывает контент — добавь `aria-busy="true"` на `<ul>` через `...rest` на время загрузки.
|
|
214
|
-
|
|
215
|
-
---
|
|
216
|
-
|
|
217
|
-
## Дизайн-токены
|
|
218
|
-
|
|
219
|
-
Нет прямых — визуал строки зависит от подкомпонентов (`ListItemSelectable`, `ListItemControls` используют собственные стили в `styles/*.module.less`).
|
|
197
|
+
### Sortable
|
|
220
198
|
|
|
221
|
-
|
|
199
|
+
`stories/List/examples/List/Sortable.tsx`
|
|
222
200
|
|
|
223
|
-
|
|
201
|
+
```tsx
|
|
202
|
+
import React, { useState } from "react";
|
|
203
|
+
import { ListSortable, ListSortableItem } from "@sberbusiness/triplex-next";
|
|
204
|
+
|
|
205
|
+
export const Sortable = () => {
|
|
206
|
+
const [items, setItems] = useState(
|
|
207
|
+
Array.from({ length: 10 }, (_, index) => ({ id: `list-sortable-item-0-${index}`, index })),
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
return (
|
|
211
|
+
<ListSortable items={items} onItemsChange={setItems}>
|
|
212
|
+
{items.map(({ id, index }) => (
|
|
213
|
+
<ListSortableItem key={id} id={id}>
|
|
214
|
+
{({ listeners, dragging, setActivatorNodeRef }) => (
|
|
215
|
+
<ListSortableItem.Target {...listeners} dragging={dragging} ref={setActivatorNodeRef}>
|
|
216
|
+
List item {index}
|
|
217
|
+
</ListSortableItem.Target>
|
|
218
|
+
)}
|
|
219
|
+
</ListSortableItem>
|
|
220
|
+
))}
|
|
221
|
+
</ListSortable>
|
|
222
|
+
);
|
|
223
|
+
};
|
|
224
|
+
```
|
|
224
225
|
|
|
225
|
-
|
|
226
|
-
- `Table` — табличный вид с колонками.
|
|
227
|
-
- `TreeView` — древовидный список.
|
|
228
|
-
- `Checkbox` — используется в `ListItemSelectable`.
|
|
229
|
-
- `LoaderScreen` — overlay для loading.
|
|
230
|
-
- `@dnd-kit/core`, `@dnd-kit/sortable`, `@dnd-kit/modifiers` — DnD-инфраструктура.
|
|
226
|
+
### WithActionItems
|
|
231
227
|
|
|
232
|
-
|
|
228
|
+
`stories/List/examples/List/WithActionItems.tsx`
|
|
233
229
|
|
|
234
|
-
|
|
230
|
+
```tsx
|
|
231
|
+
import React from "react";
|
|
232
|
+
import { List, ListActionItem } from "@sberbusiness/triplex-next";
|
|
233
|
+
|
|
234
|
+
export const WithActionItems = () => (
|
|
235
|
+
<div style={{ maxWidth: "500px" }}>
|
|
236
|
+
<List>
|
|
237
|
+
{Array.from({ length: 5 }, (_, index) => (
|
|
238
|
+
<ListActionItem
|
|
239
|
+
key={index}
|
|
240
|
+
listItemContentProps={{ onClick: () => alert(`Клик по элементу списка ${index}`) }}
|
|
241
|
+
>
|
|
242
|
+
Элемент списка {index}
|
|
243
|
+
</ListActionItem>
|
|
244
|
+
))}
|
|
245
|
+
</List>
|
|
246
|
+
</div>
|
|
247
|
+
);
|
|
248
|
+
```
|
|
235
249
|
|
|
236
|
-
|
|
237
|
-
- `stories/List/ListItem.stories.tsx`
|
|
238
|
-
- `stories/List/ListItemControls.stories.tsx`
|
|
239
|
-
- Примеры: `stories/List/examples/`
|
|
250
|
+
### SortableWithInteractiveElements
|
|
240
251
|
|
|
241
|
-
|
|
252
|
+
`stories/List/examples/List/SortableWithInteractiveElements.tsx`
|
|
242
253
|
|
|
243
|
-
|
|
254
|
+
```tsx
|
|
255
|
+
import React, { useState } from "react";
|
|
256
|
+
import { Checkbox, ListSortable, ListSortableItem, ListSortableItemControls } from "@sberbusiness/triplex-next";
|
|
257
|
+
|
|
258
|
+
export const SortableWithInteractiveElements = () => {
|
|
259
|
+
const [items, setItems] = useState(
|
|
260
|
+
Array.from({ length: 10 }, (_, index) => ({ id: `list-sortable-item-1-${index}`, index })),
|
|
261
|
+
);
|
|
262
|
+
|
|
263
|
+
return (
|
|
264
|
+
<ListSortable items={items} onItemsChange={setItems}>
|
|
265
|
+
{items.map(({ id, index }) => (
|
|
266
|
+
<ListSortableItem key={id} id={id}>
|
|
267
|
+
{({ listeners, dragging, setActivatorNodeRef }) => (
|
|
268
|
+
<ListSortableItem.Target {...listeners} dragging={dragging} ref={setActivatorNodeRef}>
|
|
269
|
+
<ListSortableItemControls>
|
|
270
|
+
<Checkbox>List item {index}</Checkbox>
|
|
271
|
+
</ListSortableItemControls>
|
|
272
|
+
</ListSortableItem.Target>
|
|
273
|
+
)}
|
|
274
|
+
</ListSortableItem>
|
|
275
|
+
))}
|
|
276
|
+
</ListSortable>
|
|
277
|
+
);
|
|
278
|
+
};
|
|
279
|
+
```
|
|
244
280
|
|
|
245
|
-
| Дата | Изменение |
|
|
246
|
-
|---|---|
|
|
247
|
-
| 2026-04-18 | Создан документ |
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
---
|
|
2
|
+
component: ListActionItem
|
|
3
|
+
category: List
|
|
4
|
+
related: [ListItem, ListItemContent, ListSortableItem]
|
|
5
|
+
tokens:
|
|
6
|
+
- --triplex-next-ListItem-Background_Hover
|
|
7
|
+
- --triplex-next-ListItem-BorderColor_Focus
|
|
8
|
+
stories: stories/List/ListItem.stories.tsx
|
|
9
|
+
version: "1.0"
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# ListActionItem
|
|
13
|
+
|
|
14
|
+
## Назначение
|
|
15
|
+
|
|
16
|
+
Самодостаточный интерактивный элемент списка. Сам рендерит `ListItem` и
|
|
17
|
+
`ListItemContent` (по аналогии с тем, как `ListSortableItem` рендерит внутри себя
|
|
18
|
+
`ListItem`), поэтому потребителю не нужно вручную составлять композицию. Добавляет
|
|
19
|
+
hover-подсветку, focus-обводку по Tab и активацию по клику / Enter / Space.
|
|
20
|
+
|
|
21
|
+
Ключевая идея: интерактивные атрибуты (`role`, `tabIndex`, обработчики) и класс
|
|
22
|
+
`styles.listActionItem` навешиваются на сам `ListItemContent`, поэтому фоном
|
|
23
|
+
управляет один элемент — базовый фон приходит из `.listItemContent`, а hover-фон
|
|
24
|
+
из `.listActionItem:hover` на том же узле. Нет перекрытия фона между обёрткой и
|
|
25
|
+
контентом.
|
|
26
|
+
|
|
27
|
+
Используй `ListActionItem` когда: строка целиком кликабельна (навигация, открытие
|
|
28
|
+
деталей, выбор из списка без чекбокса). Не используй внутри `ListTableItem` —
|
|
29
|
+
там `onClickItem` вешается напрямую на `ListItemContent` без hover/focus/keyboard.
|
|
30
|
+
|
|
31
|
+
Распределение пропсов:
|
|
32
|
+
|
|
33
|
+
- **собственные пропсы `ListActionItem`** (`extends React.LiHTMLAttributes<HTMLLIElement>`)
|
|
34
|
+
уходят на корневой `<li>` (`ListItem`) — `className`, `style`, `id`, `data-*` строки;
|
|
35
|
+
- **`listItemContentProps`** — на интерактивный `ListItemContent`: `onClick`,
|
|
36
|
+
`onKeyDown`, переопределение `role` / `tabIndex`, `className` контента и т.д.
|
|
37
|
+
|
|
38
|
+
Типичная композиция:
|
|
39
|
+
|
|
40
|
+
```tsx
|
|
41
|
+
<List>
|
|
42
|
+
<ListActionItem listItemContentProps={{ onClick: handleClick }}>Текст строки</ListActionItem>
|
|
43
|
+
</List>
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Варианты и props
|
|
49
|
+
|
|
50
|
+
### Опциональные props
|
|
51
|
+
|
|
52
|
+
| Prop | Тип | По умолчанию | Описание |
|
|
53
|
+
|---|---|---|---|
|
|
54
|
+
| `children` | `React.ReactNode` | — | Содержимое строки (рендерится внутри `ListItemContent`) |
|
|
55
|
+
| `listItemContentProps` | `React.HTMLAttributes<HTMLDivElement>` | — | Пропсы интерактивного слоя (`ListItemContent`): `onClick`, `onKeyDown`, `role`, `tabIndex`, `className` контента и т.д. |
|
|
56
|
+
| `className` | `string` | — | CSS-класс корневого `<li>` (`ListItem`) |
|
|
57
|
+
| `...HTMLLIAttributes` | — | — | Остальные атрибуты `<li>` спредятся на внутренний `ListItem` |
|
|
58
|
+
|
|
59
|
+
### Пропсы внутри `listItemContentProps`
|
|
60
|
+
|
|
61
|
+
| Prop | Тип | По умолчанию | Описание |
|
|
62
|
+
|---|---|---|---|
|
|
63
|
+
| `onClick` | `(event: React.MouseEvent<HTMLDivElement>) => void` | — | Обработчик клика по всей строке |
|
|
64
|
+
| `onKeyDown` | `(event: React.KeyboardEvent<HTMLDivElement>) => void` | — | Вызывается до внутренней обработки Enter/Space |
|
|
65
|
+
| `className` | `string` | — | Дополнительный CSS-класс, мержится с `styles.listActionItem` на `ListItemContent` |
|
|
66
|
+
| `tabIndex` | `number` | `0` | Фокусируемость по Tab. Можно переопределить (например, `-1`) |
|
|
67
|
+
| `role` | `string` | `"button"` | ARIA-роль. Можно переопределить (например, `"link"`) |
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Дизайн-токены
|
|
72
|
+
|
|
73
|
+
```text
|
|
74
|
+
--triplex-next-ListItem-Background_Hover // фон при hover (desktop)
|
|
75
|
+
--triplex-next-ListItem-BorderColor_Focus // цвет focus-обводки (:focus-visible)
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Класс `styles.listActionItem` навешивается на сам `ListItemContent`, поэтому весь
|
|
79
|
+
фон живёт на одном элементе: базовый фон и `selected`/`selectable` — из
|
|
80
|
+
`.listItemContent`, а hover-фон (`&:hover`) и focus-обводка (`&:focus-visible`) —
|
|
81
|
+
из `.listActionItem` на том же узле.
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## Инварианты
|
|
86
|
+
|
|
87
|
+
- **`forwardRef`** — обязателен, target — `HTMLDivElement` (внутренний
|
|
88
|
+
`ListItemContent`). Не убирать.
|
|
89
|
+
- **Самодостаточная композиция** — компонент сам рендерит `ListItem` →
|
|
90
|
+
`ListItemContent` (как `ListSortableItem` рендерит `ListItem`). Использовать
|
|
91
|
+
прямо внутри `List`, не оборачивать вручную в `ListItem`/`ListItemContent`.
|
|
92
|
+
- **Интерактивность на `ListItemContent`** — `role`, `tabIndex`, обработчики и
|
|
93
|
+
класс `styles.listActionItem` навешиваются на сам `ListItemContent`, а не на
|
|
94
|
+
отдельную обёртку. Это то, что позволяет одному элементу управлять фоном. Не
|
|
95
|
+
вводить промежуточный `<div>` между `ListItemContent` и hover-фоном — вернётся
|
|
96
|
+
перекрытие слоёв.
|
|
97
|
+
- **Разделение пропсов** — собственные пропсы компонента (`extends
|
|
98
|
+
LiHTMLAttributes`) идут на корневой `ListItem` (`<li>`), интерактивные — только
|
|
99
|
+
через `listItemContentProps` на `ListItemContent`. Не спредить `...rest` на
|
|
100
|
+
`ListItemContent`: тогда пропсы `<li>` и контента снова смешаются.
|
|
101
|
+
- **`role="button"` и `tabIndex={0}`** — задаются по умолчанию, но переопределяются
|
|
102
|
+
через `listItemContentProps` (`{...restListItemContentProps}` идёт после дефолтов). Не
|
|
103
|
+
убирать дефолты без обновления тестов и a11y-контракта.
|
|
104
|
+
- **Клавиатура** — Enter и Space обрабатываются через `isKey` из
|
|
105
|
+
`@sberbusiness/triplex-next/utils/keyboard`; Space вызывает `preventDefault()`,
|
|
106
|
+
затем `event.currentTarget.click()`. Не заменять на прямое сравнение `event.key`.
|
|
107
|
+
- **`data-tx`** — атрибут версии пакета, не убирать.
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Accessibility
|
|
112
|
+
|
|
113
|
+
- Элемент объявлен как `role="button"` и попадает в tab-order (`tabIndex={0}`).
|
|
114
|
+
- Активация: клик мышью, Enter, Space (через программный `click()`).
|
|
115
|
+
- Focus-индикатор — `border-color` на `:focus-visible`, `outline: none`.
|
|
116
|
+
- **`onKeyDown` вызывается до** внутренней обработки Enter/Space.
|
|
117
|
+
- **Не вкладывай другие интерактивные элементы** (кнопки, ссылки, чекбоксы) внутрь
|
|
118
|
+
`ListActionItem` — получится nested interactive elements. Для выбора с чекбоксом
|
|
119
|
+
используй `ListItemSelectable` без `ListActionItem`; для клика по всей строке в
|
|
120
|
+
табличном списке — `ListTableItem` + `onClickItem`.
|
|
121
|
+
- Для навигации по ссылке предпочтительнее нативный `<a>` / роутер-линк, а не
|
|
122
|
+
`ListActionItem` с `role="link"` — компонент не даёт семантики ссылки из коробки.
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## Связанные компоненты
|
|
127
|
+
|
|
128
|
+
- `ListItem` — `<li>`, который `ListActionItem` рендерит сам внутри себя.
|
|
129
|
+
- `ListItemContent` — слой с фоном и padding, который `ListActionItem` рендерит
|
|
130
|
+
сам и на который навешивает интерактивность. Именно он держит весь фон.
|
|
131
|
+
- `ListSortableItem` — образец паттерна: тоже самодостаточный компонент, рендерит
|
|
132
|
+
`ListItem` внутри себя.
|
|
133
|
+
- `ListItemSelectable` — альтернатива для multi-select с чекбоксом. Не комбинировать
|
|
134
|
+
с `ListActionItem` в одной строке без явной a11y-стратегии.
|
|
135
|
+
- `ListTableItem` — использует `onClickItem` на `ListItemContent`, не `ListActionItem`.
|
|
136
|
+
- `CardAction` — аналогичный паттерн интерактивной обёртки в семействе Card.
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Stories
|
|
141
|
+
|
|
142
|
+
Основные истории: `stories/List/ListItem.stories.tsx`
|
|
143
|
+
Файл примера: `stories/List/examples/ListItem/Action.tsx`
|
|
144
|
+
|
|
145
|
+
| Story | Example file | Что демонстрирует |
|
|
146
|
+
|---|---|---|
|
|
147
|
+
| `Action` | `Action.tsx` | `ListActionItem` напрямую внутри `List` |
|
|
148
|
+
| `WithActionItems` (в `List.stories.tsx`) | `examples/List/WithActionItems.tsx` | Список из нескольких кликабельных строк `ListActionItem` |
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## История изменений
|
|
153
|
+
|
|
154
|
+
| Дата | Изменение |
|
|
155
|
+
|---|---|
|
|
156
|
+
| 2026-07-17 | Добавлена story `WithActionItems` в `List.stories.tsx` — список из нескольких `ListActionItem`. |
|
|
157
|
+
| 2026-07-16 | Создан компонент `ListActionItem` — самодостаточная интерактивная строка (hover, focus по Tab, `onClick` + Enter/Space). Сам рендерит `ListItem` + `ListItemContent`; интерактивность и весь фон живут на одном `ListItemContent`. Пропсы разделены: собственные — на `<li>`, интерактивные — через `listItemContentProps`. |
|