@sberbusiness/triplex-next-mcp-server 1.44.0-beta → 1.45.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 -6
- package/bundle/components/AlertContext.md +11 -8
- package/bundle/components/AlertProcess.md +13 -10
- package/bundle/components/Badge.md +6 -3
- package/bundle/components/Button.md +46 -43
- package/bundle/components/ButtonDropdownExtended.md +3 -2
- package/bundle/components/ButtonIcon.md +7 -4
- package/bundle/components/Calendar.md +62 -59
- package/bundle/components/Caption.md +26 -23
- package/bundle/components/Checkbox.md +32 -29
- package/bundle/components/CheckboxTreeExtended.md +9 -5
- package/bundle/components/Chip.md +44 -41
- package/bundle/components/CodeText.md +26 -23
- package/bundle/components/Col.md +1 -2
- package/bundle/components/CollapsibleTree.md +8 -5
- package/bundle/components/Dropdown.md +33 -28
- package/bundle/components/EmptyView.md +12 -5
- package/bundle/components/FooterPage.md +6 -2
- package/bundle/components/FormField.md +44 -41
- package/bundle/components/FormGroup.md +192 -52
- package/bundle/components/Header.md +491 -211
- package/bundle/components/HeaderPage.md +6 -2
- package/bundle/components/ImageGallery.md +33 -34
- package/bundle/components/ImageGalleryExtended.md +33 -34
- package/bundle/components/Island.md +16 -13
- package/bundle/components/LightBox.md +14 -12
- package/bundle/components/Link.md +211 -96
- package/bundle/components/ListActionItem.md +7 -4
- package/bundle/components/ListItem.md +11 -8
- package/bundle/components/ListItemControlsButton.md +16 -13
- package/bundle/components/ListItemControlsButtonDropdown.md +9 -6
- package/bundle/components/ListItemSelectable.md +7 -4
- package/bundle/components/ListMasterFooter.md +8 -5
- package/bundle/components/ListMasterHeader.md +8 -5
- package/bundle/components/ListSortableItem.md +11 -8
- package/bundle/components/ListTableItem.md +7 -4
- package/bundle/components/LoaderMiddle.md +7 -4
- package/bundle/components/LoaderScreen.md +11 -8
- package/bundle/components/LoaderSmall.md +7 -4
- package/bundle/components/Marker.md +184 -69
- package/bundle/components/MarkerStatus.md +206 -88
- package/bundle/components/MaskedField.md +2 -4
- package/bundle/components/MasterTable.md +602 -0
- package/bundle/components/ModalWindow.md +7 -4
- package/bundle/components/MultiselectField.md +2183 -132
- package/bundle/components/Notification.md +12 -9
- package/bundle/components/Overlay.md +9 -4
- package/bundle/components/Page.md +2 -2
- package/bundle/components/Pagination.md +15 -8
- package/bundle/components/Row.md +1 -1
- package/bundle/components/SelectExtendedField.md +3 -3
- package/bundle/components/SelectField.md +466 -148
- package/bundle/components/SliderExtended.md +39 -36
- package/bundle/components/SmallInput.md +194 -51
- package/bundle/components/Suggest.md +535 -99
- package/bundle/components/SwipeableArea.md +428 -79
- package/bundle/components/TableBasic.md +19 -11
- package/bundle/components/TabsExtended.md +24 -21
- package/bundle/components/Tag.md +6 -2
- package/bundle/components/Text.md +28 -25
- package/bundle/components/TextField.md +3 -4
- package/bundle/components/ThemeProvider.md +50 -1
- package/bundle/components/Title.md +26 -23
- package/bundle/components/Tooltip.md +24 -21
- package/bundle/components/TopOverlay.md +7 -4
- package/bundle/guides/codestyle.md +3 -0
- package/bundle/guides/context.md +84 -12
- package/bundle/guides/guidelines.md +321 -0
- package/bundle/guides/template.md +22 -12
- package/bundle/manifest.json +31 -14
- package/bundle/release-notes/1.45.0.mdx +117 -0
- package/bundle/release-notes/1.46.0.mdx +7 -0
- package/bundle/tokens.json +6362 -0
- package/dist/index.js +177 -28
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,602 @@
|
|
|
1
|
+
---
|
|
2
|
+
component: MasterTable
|
|
3
|
+
category: Table
|
|
4
|
+
related:
|
|
5
|
+
- TableBasic
|
|
6
|
+
- Pagination
|
|
7
|
+
tokens: []
|
|
8
|
+
stories: stories/MasterTable/MasterTable.stories.tsx
|
|
9
|
+
version: "1.0"
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# MasterTable
|
|
13
|
+
|
|
14
|
+
## Назначение
|
|
15
|
+
|
|
16
|
+
Контейнер таблицы. Сам рисует только позиционирующую обёртку (`<div>` с `position: relative`), а его настоящая работа — собрать вокруг таблицы панели (фильтры, чипы, настройки колонок, подвал, пагинацию) и раздать им общее состояние через `MasterTableContext`: признак загрузки и текущую структуру колонок.
|
|
17
|
+
|
|
18
|
+
Используй когда: строишь табличный экран из нескольких частей — над таблицей фильтры, под ней подвал с массовыми действиями и пагинация, а состояние загрузки должно синхронно блокировать всё это.
|
|
19
|
+
|
|
20
|
+
Не используй когда: нужна только таблица без обвязки — тогда достаточно `TableBasic` (он работает и вне `MasterTable`, читая значения контекста по умолчанию). Для простых списков без колонок бери `List`.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Варианты и props
|
|
25
|
+
|
|
26
|
+
`MasterTable` — контейнер, а не управляющий компонент: он не хранит данные, страницы и фильтры. Единственное собственное состояние — массив колонок, который в него записывает `MasterTable.TableBasic`.
|
|
27
|
+
|
|
28
|
+
### Props
|
|
29
|
+
|
|
30
|
+
| Prop | Тип | По умолчанию | Описание |
|
|
31
|
+
|---|---|---|---|
|
|
32
|
+
| `loading` | `boolean` | `false` | Состояние загрузки. Не рисуется на самом контейнере, а раздаётся потомкам через контекст. |
|
|
33
|
+
| `children` | `React.ReactNode` | — | Панели и таблица. Порядок в разметке = порядок на экране, компонент ничего не переставляет. |
|
|
34
|
+
| `...rest` | `React.HTMLAttributes<HTMLDivElement>` | — | Прокидывается на корневой `<div>`. `className` объединяется с собственным классом. |
|
|
35
|
+
|
|
36
|
+
Интерфейс props — `IMasterTableProps` в `src/components/Table/TableBasic/types.ts` (там же живут интерфейсы остальных частей семейства).
|
|
37
|
+
|
|
38
|
+
### Статические субкомпоненты
|
|
39
|
+
|
|
40
|
+
Публичный API строится через статические свойства. Те же компоненты доступны и напрямую из barrel `src/components/Table/index.ts` — статики нужны для читаемой композиции.
|
|
41
|
+
|
|
42
|
+
| Субкомпонент | Роль |
|
|
43
|
+
|---|---|
|
|
44
|
+
| `MasterTable.FilterPanel` | Панель под элементы фильтрации над таблицей. |
|
|
45
|
+
| `MasterTable.ChipPanel` | Панель чипов активных фильтров; `.Links` — блок ссылок справа. |
|
|
46
|
+
| `MasterTable.TableBasicSettings` | Выпадающие настройки таблицы (видимость и порядок колонок). |
|
|
47
|
+
| `MasterTable.TableBasic` | Сама таблица. |
|
|
48
|
+
| `MasterTable.NoColumns` | Заглушка для случая, когда пользователь скрыл все колонки. |
|
|
49
|
+
| `MasterTable.TableFooter` | Подвал: `.Summary` (подытоги, выбор всех) и `.Controls` (кнопки). |
|
|
50
|
+
| `MasterTable.PaginationPanel` | Панель под пагинацию. |
|
|
51
|
+
|
|
52
|
+
### Что раздаёт контекст
|
|
53
|
+
|
|
54
|
+
`MasterTableContext` (`src/components/Table/MasterTableContext.ts`) — единственный канал связи между частями:
|
|
55
|
+
|
|
56
|
+
| Поле | Кто пишет | Кто читает |
|
|
57
|
+
|---|---|---|
|
|
58
|
+
| `loading` | `MasterTable` из props | `TableBasic` — лоадер поверх таблицы; `PaginationNavigationButton`, `PaginationPageButton`, `PaginationSelect` — блокировка элементов пагинации |
|
|
59
|
+
| `columns` | `TableBasic` в `useEffect`, из своего props `columns` | `ColumnSettings` — список колонок для настройки видимости |
|
|
60
|
+
| `setColumns` | — | `TableBasic` |
|
|
61
|
+
|
|
62
|
+
Из этого следует неочевидное: `ColumnSettings` показывает колонки, **только если** внутри того же `MasterTable` отрисован `TableBasic` — до его первого эффекта контекст пуст.
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## Дизайн-токены
|
|
67
|
+
|
|
68
|
+
Собственных токенов нет. `MasterTable.module.less` задаёт только `position: relative` — контекст позиционирования для лоадера, который `TableBasic` кладёт поверх таблицы. Визуал целиком приходит от вложенных частей (`TableBasic`, `TableFooter`, `Pagination`, `Typography`).
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Инварианты
|
|
73
|
+
|
|
74
|
+
- `forwardRef` на компоненте — не убирать. Ref идёт на корневой `<div>` (`HTMLDivElement`).
|
|
75
|
+
- Все семь статических субкомпонентов (`NoColumns`, `FilterPanel`, `ChipPanel`, `TableBasic`, `TableBasicSettings`, `TableFooter`, `PaginationPanel`) — часть публичного API. Ни переименовывать, ни удалять, ни менять их набор.
|
|
76
|
+
- `displayName` — `"MasterTable"`.
|
|
77
|
+
- Корневой элемент — `<div>`. `data-tx` выставляется **после** спреда `...rest`, поэтому потребитель не может его переопределить — это осознанное поведение, общее для семейства (`FilterPanel`, `PaginationPanel`).
|
|
78
|
+
- Все обёртки семейства (`FilterPanel`, `ChipPanel`, `ChipPanelLinks`, `NoColumns`, `PaginationPanel`, `TableFooter`, `TableBasicSettings`) пробрасывают `ref` на свой корневой элемент — не убирать. У составных (`ChipPanel`, `TableFooter`, `TableBasicSettings`) статики объявлены через `Object.assign` с явным интерфейсом: при добавлении или удалении статики интерфейс нужно править синхронно, иначе TypeScript промолчит.
|
|
79
|
+
- Собственный класс `styles.masterTable` объединяется с пользовательским `className` через `clsx`, а не затирается им.
|
|
80
|
+
- Значение контекста мемоизировано (`useMemo` по `columns` и `loading`) — не откатывать: возврат к новому объекту на каждый рендер снова заставит `useEffect` в `TableBasic` перезапускаться на каждый рендер контейнера.
|
|
81
|
+
- Колонки потребитель обязан заменять иммутабельно (`map`/спред, а не мутация на месте): эффект в `TableBasic` сравнивает `isEqual(columns, context.columns)`, а после первой синхронизации в контексте лежит **та же ссылка**, что пришла в props, — поэтому мутация на месте до `ColumnSettings` не доходит. К мемоизации это отношения не имеет, ограничение существовало и до неё; откат `useMemo` мутации не «починит».
|
|
82
|
+
- Статики перечислены в `IMasterTableStatics` вручную. Явная аннотация типа снимает проверку лишних свойств у `Object.assign`, поэтому набор в интерфейсе и в `Object.assign` правится синхронно — иначе новая статика не попадёт в публичный тип.
|
|
83
|
+
- `IMasterTableProps` объявлен в `src/components/Table/TableBasic/types.ts`, а не рядом с компонентом. Расположение историческое; перенос — breaking change для тех, кто импортирует тип по глубокому пути.
|
|
84
|
+
- Компонент React 17-совместим: никакого `useId`/`useSyncExternalStore` — код синхронизируется в ветку `release-0`.
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## Accessibility
|
|
89
|
+
|
|
90
|
+
Собственной семантики контейнер не добавляет: это `<div>` без роли, вся доступность живёт в частях (таблица — `<table>` с заголовками, пагинация — `<nav>` со списком, настройки — `aria-haspopup`/`aria-expanded` на ссылке-триггере).
|
|
91
|
+
|
|
92
|
+
Что важно для потребителя:
|
|
93
|
+
|
|
94
|
+
- `loading` — визуальное и функциональное состояние, но не объявляемое: скринридер о начале загрузки не узнает. Если это критично, добавь свой `aria-live`-регион рядом с таблицей.
|
|
95
|
+
- Роль и подпись контейнера (`role`, `aria-label`) не хардкодятся — библиотека мультиязычная, передавай их сам через `...rest`, если экран того требует.
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## Связанные компоненты
|
|
100
|
+
|
|
101
|
+
- `TableBasic` — сама таблица; единственная часть, которая пишет в контекст (`columns`). Работает и вне `MasterTable`, но тогда `loading` всегда `false`.
|
|
102
|
+
- `Pagination` — обычно кладётся в `MasterTable.PaginationPanel`. Её кнопки и селект читают `MasterTableContext.loading` и блокируются в загрузке; это опора на деталь `MasterTable`, поэтому связь зафиксирована с обеих сторон.
|
|
103
|
+
- `FilterPanel`, `ChipPanel` (`.Links`), `PaginationPanel`, `NoColumns` — тривиальные обёртки: `clsx(className)` + спред + собственный класс, без своей логики. Отличаются только стилями и тем, что `FilterPanel` и `PaginationPanel` выставляют `data-tx`. Все пробрасывают `ref` на свой корневой `<div>`; отдельных строк в `docs/ai/ROADMAP.md` у них нет — состояние учитывается здесь.
|
|
104
|
+
- `TableFooter` — подвал поверх `FooterDescription`; `.Summary` даёт `.SelectedCount`, `.SelectAllButton` и `.Amount`, `.Controls` — это `FooterDescriptionControls` из семейства `Footer`.
|
|
105
|
+
- `TableBasicSettings` — выпадающие настройки поверх `ButtonDropdownExtended`; внутри `ColumnSettings` читает `columns` из контекста и поддерживает children-функцию `({ columns }) => ...`. Его `ref` идёт на корневой `<div>`, который рисует `ButtonDropdownExtended`, — поэтому `forwardRef` пришлось добавить и туда.
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## Stories
|
|
110
|
+
|
|
111
|
+
Основные истории: `stories/MasterTable/MasterTable.stories.tsx`
|
|
112
|
+
Файлы примеров: `stories/MasterTable/examples/`
|
|
113
|
+
|
|
114
|
+
| Story | Example file | Что демонстрирует |
|
|
115
|
+
|---|---|---|
|
|
116
|
+
| `Playground` | `Playground.tsx` | Интерактивный контроль `loading` и набора панелей (фильтры, подвал, пагинация, пустая таблица) |
|
|
117
|
+
| `Default` | `Default.tsx` | Минимальная композиция: контейнер + `MasterTable.TableBasic` |
|
|
118
|
+
| `Loading` | `Loading.tsx` | `loading` — лоадер поверх заполненной таблицы |
|
|
119
|
+
| `NoData` | `NoData.tsx` | Пустой `data` — таблица показывает `renderNoData` |
|
|
120
|
+
| `NoColumns` | `NoColumns.tsx` | Все колонки с `hidden` — таблица показывает `renderNoColumns` с `MasterTable.NoColumns` |
|
|
121
|
+
| `Example` | `Example.tsx` | Production-like композиция: фильтры, настройки колонок, сортировка, выбор строк, подвал с подытогом, пагинация |
|
|
122
|
+
| `VisualTests` | `VisualTests.tsx` | Скриншот-регрессия: загрузка поверх пустой таблицы, выделенная строка, пагинация, заблокированная загрузкой |
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## История изменений
|
|
127
|
+
|
|
128
|
+
| Дата | Изменение |
|
|
129
|
+
|---|---|
|
|
130
|
+
| 2026-08-31 | Обёртки семейства (`FilterPanel`, `ChipPanel` + `.Links`, `NoColumns`, `PaginationPanel`, `TableFooter`, `TableBasicSettings`) переведены с `React.FC` на `forwardRef`, у составных статики — через `Object.assign`. Добавлены smoke-тесты на каждую. Ради `TableBasicSettings` `forwardRef` добавлен и в `ButtonDropdownExtended`. Набор props и DOM не изменились. |
|
|
131
|
+
| 2026-08-27 | Создан документ. Компонент переведён с `React.FC` на `Object.assign(forwardRef(...), {...})` — теперь пробрасывает `ref` на корневой `<div>`; значение контекста мемоизировано. Публичный API не изменился. |
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## Примеры использования
|
|
136
|
+
|
|
137
|
+
### Default
|
|
138
|
+
|
|
139
|
+
`stories/MasterTable/examples/Default.tsx`
|
|
140
|
+
|
|
141
|
+
```tsx
|
|
142
|
+
import React from "react";
|
|
143
|
+
import { EHorizontalAlign, ITableBasicColumn, ITableBasicRow, MasterTable } from "@sberbusiness/triplex-next";
|
|
144
|
+
|
|
145
|
+
const columns: ITableBasicColumn[] = [
|
|
146
|
+
{ fieldKey: "docNumber", label: "Номер", width: "15%" },
|
|
147
|
+
{ fieldKey: "recipient", label: "Получатель", width: "45%" },
|
|
148
|
+
{ fieldKey: "status", label: "Статус", width: "20%" },
|
|
149
|
+
{ fieldKey: "sum", label: "Сумма", width: "20%", horizontalAlign: EHorizontalAlign.RIGHT },
|
|
150
|
+
];
|
|
151
|
+
|
|
152
|
+
const data: ITableBasicRow[] = [
|
|
153
|
+
{
|
|
154
|
+
rowKey: "1350",
|
|
155
|
+
rowData: { docNumber: "1350", recipient: "ООО Ромашка", status: "Создан", sum: "12 500,00 ₽" },
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
rowKey: "1351",
|
|
159
|
+
rowData: { docNumber: "1351", recipient: "ИП Иванов Иван Иванович", status: "Подписан", sum: "8 300,00 ₽" },
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
rowKey: "1352",
|
|
163
|
+
rowData: { docNumber: "1352", recipient: "ООО Василёк", status: "Оплачен", sum: "145 000,00 ₽" },
|
|
164
|
+
},
|
|
165
|
+
];
|
|
166
|
+
|
|
167
|
+
export const Default = () => (
|
|
168
|
+
<MasterTable>
|
|
169
|
+
<MasterTable.TableBasic columns={columns} data={data} renderNoData={() => <div>Нет данных</div>} />
|
|
170
|
+
</MasterTable>
|
|
171
|
+
);
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Loading
|
|
175
|
+
|
|
176
|
+
`stories/MasterTable/examples/Loading.tsx`
|
|
177
|
+
|
|
178
|
+
```tsx
|
|
179
|
+
import React from "react";
|
|
180
|
+
import { EHorizontalAlign, ITableBasicColumn, ITableBasicRow, MasterTable } from "@sberbusiness/triplex-next";
|
|
181
|
+
|
|
182
|
+
const columns: ITableBasicColumn[] = [
|
|
183
|
+
{ fieldKey: "docNumber", label: "Номер", width: "15%" },
|
|
184
|
+
{ fieldKey: "recipient", label: "Получатель", width: "45%" },
|
|
185
|
+
{ fieldKey: "status", label: "Статус", width: "20%" },
|
|
186
|
+
{ fieldKey: "sum", label: "Сумма", width: "20%", horizontalAlign: EHorizontalAlign.RIGHT },
|
|
187
|
+
];
|
|
188
|
+
|
|
189
|
+
const data: ITableBasicRow[] = [
|
|
190
|
+
{
|
|
191
|
+
rowKey: "1350",
|
|
192
|
+
rowData: { docNumber: "1350", recipient: "ООО Ромашка", status: "Создан", sum: "12 500,00 ₽" },
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
rowKey: "1351",
|
|
196
|
+
rowData: { docNumber: "1351", recipient: "ИП Иванов Иван Иванович", status: "Подписан", sum: "8 300,00 ₽" },
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
rowKey: "1352",
|
|
200
|
+
rowData: { docNumber: "1352", recipient: "ООО Василёк", status: "Оплачен", sum: "145 000,00 ₽" },
|
|
201
|
+
},
|
|
202
|
+
];
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Свойство loading раздаётся дочерним компонентам через MasterTableContext:
|
|
206
|
+
* таблица закрывается лоадером.
|
|
207
|
+
*/
|
|
208
|
+
export const Loading = () => (
|
|
209
|
+
<MasterTable loading>
|
|
210
|
+
<MasterTable.TableBasic columns={columns} data={data} renderNoData={() => <div>Нет данных</div>} />
|
|
211
|
+
</MasterTable>
|
|
212
|
+
);
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### NoData
|
|
216
|
+
|
|
217
|
+
`stories/MasterTable/examples/NoData.tsx`
|
|
218
|
+
|
|
219
|
+
```tsx
|
|
220
|
+
import React from "react";
|
|
221
|
+
import { EmptytableSysIcon96 } from "@sberbusiness/icons-next";
|
|
222
|
+
import {
|
|
223
|
+
EComponentSize,
|
|
224
|
+
EFontType,
|
|
225
|
+
EFontWeightTitle,
|
|
226
|
+
EHorizontalAlign,
|
|
227
|
+
ETextSize,
|
|
228
|
+
ETitleSize,
|
|
229
|
+
Button,
|
|
230
|
+
EButtonTheme,
|
|
231
|
+
Gap,
|
|
232
|
+
ITableBasicColumn,
|
|
233
|
+
MasterTable,
|
|
234
|
+
Text,
|
|
235
|
+
Title,
|
|
236
|
+
} from "@sberbusiness/triplex-next";
|
|
237
|
+
|
|
238
|
+
const columns: ITableBasicColumn[] = [
|
|
239
|
+
{ fieldKey: "docNumber", label: "Номер", width: "15%" },
|
|
240
|
+
{ fieldKey: "recipient", label: "Получатель", width: "45%" },
|
|
241
|
+
{ fieldKey: "status", label: "Статус", width: "20%" },
|
|
242
|
+
{ fieldKey: "sum", label: "Сумма", width: "20%", horizontalAlign: EHorizontalAlign.RIGHT },
|
|
243
|
+
];
|
|
244
|
+
|
|
245
|
+
/** Заглушка, которую таблица показывает вместо строк, когда data пустой. */
|
|
246
|
+
const renderNoData = () => (
|
|
247
|
+
<>
|
|
248
|
+
<EmptytableSysIcon96 />
|
|
249
|
+
<Gap size={8} />
|
|
250
|
+
<Title size={ETitleSize.H3} weight={EFontWeightTitle.REGULAR}>
|
|
251
|
+
Здесь пока пусто
|
|
252
|
+
</Title>
|
|
253
|
+
<Gap size={12} />
|
|
254
|
+
<Text tag="div" size={ETextSize.B3} type={EFontType.SECONDARY}>
|
|
255
|
+
Создайте первый документ, и он появится в таблице.
|
|
256
|
+
</Text>
|
|
257
|
+
<Gap size={24} />
|
|
258
|
+
<Button theme={EButtonTheme.GENERAL} size={EComponentSize.MD}>
|
|
259
|
+
Создать документ
|
|
260
|
+
</Button>
|
|
261
|
+
</>
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
export const NoData = () => (
|
|
265
|
+
<MasterTable>
|
|
266
|
+
<MasterTable.TableBasic columns={columns} data={[]} renderNoData={renderNoData} />
|
|
267
|
+
</MasterTable>
|
|
268
|
+
);
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### NoColumns
|
|
272
|
+
|
|
273
|
+
`stories/MasterTable/examples/NoColumns.tsx`
|
|
274
|
+
|
|
275
|
+
```tsx
|
|
276
|
+
import React from "react";
|
|
277
|
+
import {
|
|
278
|
+
EComponentSize,
|
|
279
|
+
EFontType,
|
|
280
|
+
EFontWeightTitle,
|
|
281
|
+
EHorizontalAlign,
|
|
282
|
+
ETextSize,
|
|
283
|
+
ETitleSize,
|
|
284
|
+
Button,
|
|
285
|
+
EButtonTheme,
|
|
286
|
+
Gap,
|
|
287
|
+
ITableBasicColumn,
|
|
288
|
+
ITableBasicRow,
|
|
289
|
+
MasterTable,
|
|
290
|
+
Text,
|
|
291
|
+
Title,
|
|
292
|
+
} from "@sberbusiness/triplex-next";
|
|
293
|
+
|
|
294
|
+
/** Все колонки скрыты — пользователь снял их в настройках таблицы. */
|
|
295
|
+
const columns: ITableBasicColumn[] = [
|
|
296
|
+
{ fieldKey: "docNumber", label: "Номер", width: "15%", hidden: true },
|
|
297
|
+
{ fieldKey: "recipient", label: "Получатель", width: "45%", hidden: true },
|
|
298
|
+
{ fieldKey: "status", label: "Статус", width: "20%", hidden: true },
|
|
299
|
+
{ fieldKey: "sum", label: "Сумма", width: "20%", horizontalAlign: EHorizontalAlign.RIGHT, hidden: true },
|
|
300
|
+
];
|
|
301
|
+
|
|
302
|
+
const data: ITableBasicRow[] = [
|
|
303
|
+
{
|
|
304
|
+
rowKey: "1350",
|
|
305
|
+
rowData: { docNumber: "1350", recipient: "ООО Ромашка", status: "Создан", sum: "12 500,00 ₽" },
|
|
306
|
+
},
|
|
307
|
+
{
|
|
308
|
+
rowKey: "1351",
|
|
309
|
+
rowData: { docNumber: "1351", recipient: "ИП Иванов Иван Иванович", status: "Подписан", sum: "8 300,00 ₽" },
|
|
310
|
+
},
|
|
311
|
+
];
|
|
312
|
+
|
|
313
|
+
/** Заглушка, которую таблица показывает вместо разметки, когда каждая колонка имеет hidden. */
|
|
314
|
+
const renderNoColumns = () => (
|
|
315
|
+
<MasterTable.NoColumns>
|
|
316
|
+
<Title size={ETitleSize.H3} weight={EFontWeightTitle.REGULAR}>
|
|
317
|
+
Все колонки таблицы скрыты
|
|
318
|
+
</Title>
|
|
319
|
+
<Gap size={12} />
|
|
320
|
+
<Text tag="div" size={ETextSize.B3} type={EFontType.SECONDARY}>
|
|
321
|
+
Выберите нужные вам для отображения колонки в настройках таблицы.
|
|
322
|
+
</Text>
|
|
323
|
+
<Gap size={24} />
|
|
324
|
+
<Button theme={EButtonTheme.GENERAL} size={EComponentSize.MD}>
|
|
325
|
+
Сбросить настройки
|
|
326
|
+
</Button>
|
|
327
|
+
</MasterTable.NoColumns>
|
|
328
|
+
);
|
|
329
|
+
|
|
330
|
+
export const NoColumns = () => (
|
|
331
|
+
<MasterTable>
|
|
332
|
+
<MasterTable.TableBasic
|
|
333
|
+
columns={columns}
|
|
334
|
+
data={data}
|
|
335
|
+
renderNoColumns={renderNoColumns}
|
|
336
|
+
renderNoData={() => <div>Нет данных</div>}
|
|
337
|
+
/>
|
|
338
|
+
</MasterTable>
|
|
339
|
+
);
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
### Example
|
|
343
|
+
|
|
344
|
+
`stories/MasterTable/examples/Example.tsx`
|
|
345
|
+
|
|
346
|
+
```tsx
|
|
347
|
+
import React, { useState } from "react";
|
|
348
|
+
import { EmptytableSysIcon96 } from "@sberbusiness/icons-next";
|
|
349
|
+
import {
|
|
350
|
+
Amount,
|
|
351
|
+
Button,
|
|
352
|
+
Checkbox,
|
|
353
|
+
ColumnSettings,
|
|
354
|
+
ECellType,
|
|
355
|
+
EButtonTheme,
|
|
356
|
+
EComponentSize,
|
|
357
|
+
EFontType,
|
|
358
|
+
EFontWeightTitle,
|
|
359
|
+
EHorizontalAlign,
|
|
360
|
+
EOrderDirection,
|
|
361
|
+
ETextSize,
|
|
362
|
+
ETitleSize,
|
|
363
|
+
Gap,
|
|
364
|
+
ISelectFieldOption,
|
|
365
|
+
ISortOrder,
|
|
366
|
+
ITableBasicColumn,
|
|
367
|
+
ITableBasicRow,
|
|
368
|
+
MasterTable,
|
|
369
|
+
Pagination,
|
|
370
|
+
Text,
|
|
371
|
+
TextField,
|
|
372
|
+
Title,
|
|
373
|
+
} from "@sberbusiness/triplex-next";
|
|
374
|
+
|
|
375
|
+
/** Документы таблицы. Данные захардкожены, чтобы пример был воспроизводимым. */
|
|
376
|
+
const DOCUMENTS = [
|
|
377
|
+
{ docNumber: "1350", recipient: "ООО Ромашка", status: "Создан", sum: "12500.00" },
|
|
378
|
+
{ docNumber: "1351", recipient: "ИП Иванов Иван Иванович", status: "Подписан", sum: "8300.00" },
|
|
379
|
+
{ docNumber: "1352", recipient: "ООО Василёк", status: "Оплачен", sum: "145000.00" },
|
|
380
|
+
{ docNumber: "1353", recipient: "ООО Ромашка", status: "Создан", sum: "3200.00" },
|
|
381
|
+
];
|
|
382
|
+
|
|
383
|
+
const PAGE_SIZE_OPTIONS: ISelectFieldOption[] = [
|
|
384
|
+
{ id: "2", value: "2", label: "2" },
|
|
385
|
+
{ id: "4", value: "4", label: "4" },
|
|
386
|
+
];
|
|
387
|
+
|
|
388
|
+
const INITIAL_COLUMNS: ITableBasicColumn[] = [
|
|
389
|
+
{ fieldKey: "selection", label: "", width: "48px", cellType: ECellType.CHECKBOX },
|
|
390
|
+
{ fieldKey: "docNumber", label: "Номер", width: "15%", orderDirection: EOrderDirection.NONE },
|
|
391
|
+
{ fieldKey: "recipient", label: "Получатель", width: "40%" },
|
|
392
|
+
{ fieldKey: "status", label: "Статус", width: "20%" },
|
|
393
|
+
{
|
|
394
|
+
fieldKey: "sum",
|
|
395
|
+
label: "Сумма",
|
|
396
|
+
width: "20%",
|
|
397
|
+
horizontalAlign: EHorizontalAlign.RIGHT,
|
|
398
|
+
orderDirection: EOrderDirection.NONE,
|
|
399
|
+
renderCell: (value) => <Amount value={String(value)} currency="RUB" />,
|
|
400
|
+
},
|
|
401
|
+
];
|
|
402
|
+
|
|
403
|
+
const renderNoData = () => (
|
|
404
|
+
<>
|
|
405
|
+
<EmptytableSysIcon96 />
|
|
406
|
+
<Gap size={8} />
|
|
407
|
+
<Title size={ETitleSize.H3} weight={EFontWeightTitle.REGULAR}>
|
|
408
|
+
Здесь пока пусто
|
|
409
|
+
</Title>
|
|
410
|
+
<Gap size={12} />
|
|
411
|
+
<Text tag="div" size={ETextSize.B3} type={EFontType.SECONDARY}>
|
|
412
|
+
Измените параметры фильтрации, чтобы увидеть документы.
|
|
413
|
+
</Text>
|
|
414
|
+
</>
|
|
415
|
+
);
|
|
416
|
+
|
|
417
|
+
export const Example = () => {
|
|
418
|
+
const [columns, setColumns] = useState<ITableBasicColumn[]>(INITIAL_COLUMNS);
|
|
419
|
+
const [selectedKeys, setSelectedKeys] = useState<string[]>(["1351"]);
|
|
420
|
+
const [settingsOpened, setSettingsOpened] = useState(false);
|
|
421
|
+
const [recipientFilter, setRecipientFilter] = useState("");
|
|
422
|
+
const [sortOrder, setSortOrder] = useState<ISortOrder | null>(null);
|
|
423
|
+
const [pageSize, setPageSize] = useState(PAGE_SIZE_OPTIONS[1]);
|
|
424
|
+
const [currentPage, setCurrentPage] = useState(1);
|
|
425
|
+
|
|
426
|
+
const toggleRow = (rowKey: string) =>
|
|
427
|
+
setSelectedKeys((keys) => (keys.includes(rowKey) ? keys.filter((key) => key !== rowKey) : [...keys, rowKey]));
|
|
428
|
+
|
|
429
|
+
const toggleColumn = (fieldKey: string, visible: boolean) =>
|
|
430
|
+
setColumns((current) =>
|
|
431
|
+
current.map((column) => (column.fieldKey === fieldKey ? { ...column, hidden: !visible } : column)),
|
|
432
|
+
);
|
|
433
|
+
|
|
434
|
+
const handleOrderBy = ({ fieldKey, direction }: ISortOrder) => {
|
|
435
|
+
setSortOrder(direction === EOrderDirection.NONE ? null : { fieldKey, direction });
|
|
436
|
+
setCurrentPage(1);
|
|
437
|
+
setColumns((current) =>
|
|
438
|
+
current.map((column) =>
|
|
439
|
+
column.fieldKey === fieldKey
|
|
440
|
+
? { ...column, orderDirection: direction }
|
|
441
|
+
: { ...column, orderDirection: column.orderDirection ? EOrderDirection.NONE : undefined },
|
|
442
|
+
),
|
|
443
|
+
);
|
|
444
|
+
};
|
|
445
|
+
|
|
446
|
+
const handlePageSizeChange = (option: ISelectFieldOption) => {
|
|
447
|
+
setPageSize(option);
|
|
448
|
+
setCurrentPage(1);
|
|
449
|
+
};
|
|
450
|
+
|
|
451
|
+
const handleFilterChange = (value: string) => {
|
|
452
|
+
setRecipientFilter(value);
|
|
453
|
+
setCurrentPage(1);
|
|
454
|
+
};
|
|
455
|
+
|
|
456
|
+
// Фильтрация, сортировка и постраничная нарезка — работа потребителя: таблица только отображает то, что ей передали.
|
|
457
|
+
const filtered = DOCUMENTS.filter((document) =>
|
|
458
|
+
document.recipient.toLowerCase().includes(recipientFilter.trim().toLowerCase()),
|
|
459
|
+
);
|
|
460
|
+
|
|
461
|
+
const sorted = sortOrder
|
|
462
|
+
? [...filtered].sort((left, right) => {
|
|
463
|
+
const comparison = String(left[sortOrder.fieldKey as keyof typeof left]).localeCompare(
|
|
464
|
+
String(right[sortOrder.fieldKey as keyof typeof right]),
|
|
465
|
+
"ru",
|
|
466
|
+
{ numeric: true },
|
|
467
|
+
);
|
|
468
|
+
|
|
469
|
+
return sortOrder.direction === EOrderDirection.DESC ? -comparison : comparison;
|
|
470
|
+
})
|
|
471
|
+
: filtered;
|
|
472
|
+
|
|
473
|
+
const selectedSum = DOCUMENTS.filter((document) => selectedKeys.includes(document.docNumber))
|
|
474
|
+
.reduce((total, document) => total + Number(document.sum), 0)
|
|
475
|
+
.toFixed(2);
|
|
476
|
+
|
|
477
|
+
const rowsPerPage = Number(pageSize.value);
|
|
478
|
+
const totalPages = Math.max(1, Math.ceil(sorted.length / rowsPerPage));
|
|
479
|
+
const pageDocuments = sorted.slice((currentPage - 1) * rowsPerPage, currentPage * rowsPerPage);
|
|
480
|
+
|
|
481
|
+
const data: ITableBasicRow[] = pageDocuments.map((document) => ({
|
|
482
|
+
rowKey: document.docNumber,
|
|
483
|
+
selected: selectedKeys.includes(document.docNumber),
|
|
484
|
+
rowData: {
|
|
485
|
+
...document,
|
|
486
|
+
selection: (
|
|
487
|
+
<Checkbox
|
|
488
|
+
checked={selectedKeys.includes(document.docNumber)}
|
|
489
|
+
onChange={() => toggleRow(document.docNumber)}
|
|
490
|
+
aria-label={`Выбрать документ ${document.docNumber}`}
|
|
491
|
+
/>
|
|
492
|
+
),
|
|
493
|
+
},
|
|
494
|
+
}));
|
|
495
|
+
|
|
496
|
+
return (
|
|
497
|
+
<MasterTable>
|
|
498
|
+
<MasterTable.FilterPanel>
|
|
499
|
+
<TextField
|
|
500
|
+
size={EComponentSize.MD}
|
|
501
|
+
label="Получатель"
|
|
502
|
+
inputProps={{
|
|
503
|
+
value: recipientFilter,
|
|
504
|
+
onChange: (event: React.ChangeEvent<HTMLInputElement>) =>
|
|
505
|
+
handleFilterChange(event.target.value),
|
|
506
|
+
}}
|
|
507
|
+
/>
|
|
508
|
+
</MasterTable.FilterPanel>
|
|
509
|
+
|
|
510
|
+
<MasterTable.ChipPanel>
|
|
511
|
+
<MasterTable.ChipPanel.Links>
|
|
512
|
+
<MasterTable.TableBasicSettings
|
|
513
|
+
linkTitle={settingsOpened ? "Скрыть настройки" : "Настройки"}
|
|
514
|
+
opened={settingsOpened}
|
|
515
|
+
setOpened={setSettingsOpened}
|
|
516
|
+
>
|
|
517
|
+
<MasterTable.TableBasicSettings.Header>Показать столбцы</MasterTable.TableBasicSettings.Header>
|
|
518
|
+
<MasterTable.TableBasicSettings.Body>
|
|
519
|
+
<ColumnSettings>
|
|
520
|
+
{({ columns: contextColumns }) =>
|
|
521
|
+
contextColumns
|
|
522
|
+
.filter((column) => column.fieldKey !== "selection")
|
|
523
|
+
.map((column) => (
|
|
524
|
+
<Checkbox
|
|
525
|
+
key={column.fieldKey}
|
|
526
|
+
checked={column.hidden !== true}
|
|
527
|
+
onChange={(event) =>
|
|
528
|
+
toggleColumn(column.fieldKey, event.target.checked)
|
|
529
|
+
}
|
|
530
|
+
>
|
|
531
|
+
{column.label}
|
|
532
|
+
</Checkbox>
|
|
533
|
+
))
|
|
534
|
+
}
|
|
535
|
+
</ColumnSettings>
|
|
536
|
+
</MasterTable.TableBasicSettings.Body>
|
|
537
|
+
<MasterTable.TableBasicSettings.Footer>
|
|
538
|
+
<Button
|
|
539
|
+
theme={EButtonTheme.LINK}
|
|
540
|
+
size={EComponentSize.MD}
|
|
541
|
+
onClick={() => {
|
|
542
|
+
setColumns(INITIAL_COLUMNS);
|
|
543
|
+
setSortOrder(null);
|
|
544
|
+
}}
|
|
545
|
+
>
|
|
546
|
+
Сбросить настройки
|
|
547
|
+
</Button>
|
|
548
|
+
</MasterTable.TableBasicSettings.Footer>
|
|
549
|
+
</MasterTable.TableBasicSettings>
|
|
550
|
+
</MasterTable.ChipPanel.Links>
|
|
551
|
+
</MasterTable.ChipPanel>
|
|
552
|
+
|
|
553
|
+
<MasterTable.TableBasic
|
|
554
|
+
columns={columns}
|
|
555
|
+
data={data}
|
|
556
|
+
highlightRowOnHover
|
|
557
|
+
onOrderBy={handleOrderBy}
|
|
558
|
+
renderNoData={renderNoData}
|
|
559
|
+
/>
|
|
560
|
+
|
|
561
|
+
<MasterTable.TableFooter>
|
|
562
|
+
<MasterTable.TableFooter.Summary>
|
|
563
|
+
<MasterTable.TableFooter.Summary.SelectedCount>
|
|
564
|
+
Выбрано {selectedKeys.length} из {DOCUMENTS.length}
|
|
565
|
+
</MasterTable.TableFooter.Summary.SelectedCount>
|
|
566
|
+
<MasterTable.TableFooter.Summary.SelectAllButton
|
|
567
|
+
onClick={() => setSelectedKeys(DOCUMENTS.map((document) => document.docNumber))}
|
|
568
|
+
>
|
|
569
|
+
Выбрать все
|
|
570
|
+
</MasterTable.TableFooter.Summary.SelectAllButton>
|
|
571
|
+
<MasterTable.TableFooter.Summary.Amount label="Сумма" sum={selectedSum} currency="RUB" />
|
|
572
|
+
</MasterTable.TableFooter.Summary>
|
|
573
|
+
<MasterTable.TableFooter.Controls>
|
|
574
|
+
<Button theme={EButtonTheme.SECONDARY} size={EComponentSize.MD}>
|
|
575
|
+
Скачать
|
|
576
|
+
</Button>
|
|
577
|
+
<Button theme={EButtonTheme.GENERAL} size={EComponentSize.MD}>
|
|
578
|
+
Подписать
|
|
579
|
+
</Button>
|
|
580
|
+
</MasterTable.TableFooter.Controls>
|
|
581
|
+
</MasterTable.TableFooter>
|
|
582
|
+
|
|
583
|
+
<MasterTable.PaginationPanel>
|
|
584
|
+
<Pagination
|
|
585
|
+
paginationSelectProps={{
|
|
586
|
+
paginationLabel: "Показывать по",
|
|
587
|
+
options: PAGE_SIZE_OPTIONS,
|
|
588
|
+
value: pageSize,
|
|
589
|
+
onChange: handlePageSizeChange,
|
|
590
|
+
}}
|
|
591
|
+
paginationNavigationProps={{
|
|
592
|
+
currentPage,
|
|
593
|
+
totalPages,
|
|
594
|
+
onCurrentPageChange: setCurrentPage,
|
|
595
|
+
}}
|
|
596
|
+
/>
|
|
597
|
+
</MasterTable.PaginationPanel>
|
|
598
|
+
</MasterTable>
|
|
599
|
+
);
|
|
600
|
+
};
|
|
601
|
+
```
|
|
602
|
+
|
|
@@ -3,8 +3,8 @@ component: ModalWindow
|
|
|
3
3
|
category: ModalWindow
|
|
4
4
|
related: [ModalWindowContent, ModalWindowHeader, ModalWindowBody, ModalWindowFooter, ModalWindowClose, ModalWindowViewManager, Portal, FocusTrap, Page, HeaderPage, FooterPage, Island, LoaderScreen]
|
|
5
5
|
tokens:
|
|
6
|
-
-
|
|
7
|
-
-
|
|
6
|
+
- ModalWindow.Backdrop_Background
|
|
7
|
+
- ModalWindow.Background
|
|
8
8
|
stories: stories/ModalWindow/ModalWindow.stories.tsx
|
|
9
9
|
version: "1.0"
|
|
10
10
|
---
|
|
@@ -53,9 +53,12 @@ version: "1.0"
|
|
|
53
53
|
|
|
54
54
|
## Дизайн-токены
|
|
55
55
|
|
|
56
|
+
Переопределяются через `ThemeProvider` (prop `tokens`) — см. `ThemeProvider-ai.md` →
|
|
57
|
+
«Как переопределять токены». Значения по умолчанию — `src/components/DesignTokens/components/ModalWindow.ts`.
|
|
58
|
+
|
|
56
59
|
```text
|
|
57
|
-
|
|
58
|
-
|
|
60
|
+
ModalWindow.Backdrop_Background
|
|
61
|
+
ModalWindow.Background
|
|
59
62
|
```
|
|
60
63
|
|
|
61
64
|
Помимо токенов, компонент инжектит и читает несколько runtime CSS-переменных, не относящихся к дизайн-системе:
|