@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.
Files changed (73) hide show
  1. package/README.md +10 -0
  2. package/bundle/components/AlertContext.md +268 -0
  3. package/bundle/components/BodyPage.md +186 -0
  4. package/bundle/components/Button.md +93 -85
  5. package/bundle/components/ButtonDropdown.md +36 -70
  6. package/bundle/components/ButtonDropdownExtended.md +422 -0
  7. package/bundle/components/ButtonIcon.md +41 -48
  8. package/bundle/components/Caption.md +291 -0
  9. package/bundle/components/Checkbox.md +43 -122
  10. package/bundle/components/CheckboxXGroup.md +109 -46
  11. package/bundle/components/CheckboxYGroup.md +77 -49
  12. package/bundle/components/CodeText.md +197 -0
  13. package/bundle/components/Col.md +311 -162
  14. package/bundle/components/CollapsibleTree.md +584 -0
  15. package/bundle/components/CollapsibleTreeExtended.md +422 -0
  16. package/bundle/components/Dropdown.md +748 -346
  17. package/bundle/components/EmptyView.md +14 -15
  18. package/bundle/components/FooterPage.md +203 -0
  19. package/bundle/components/Gap.md +115 -57
  20. package/bundle/components/HeaderPage.md +226 -0
  21. package/bundle/components/HelpBox.md +45 -84
  22. package/bundle/components/ImageGallery.md +423 -0
  23. package/bundle/components/ImageGalleryExtended.md +606 -0
  24. package/bundle/components/LightBox.md +2252 -233
  25. package/bundle/components/List.md +216 -183
  26. package/bundle/components/ListActionItem.md +157 -0
  27. package/bundle/components/ListItem.md +276 -0
  28. package/bundle/components/ListItemControlsButton.md +103 -0
  29. package/bundle/components/ListItemControlsButtonDropdown.md +107 -0
  30. package/bundle/components/ListItemSelectable.md +99 -0
  31. package/bundle/components/ListItemTable.md +390 -0
  32. package/bundle/components/ListMaster.md +876 -156
  33. package/bundle/components/ListMasterFooter.md +80 -0
  34. package/bundle/components/ListMasterHeader.md +86 -0
  35. package/bundle/components/ListSortable.md +153 -0
  36. package/bundle/components/ListSortableItem.md +175 -0
  37. package/bundle/components/ListTableItem.md +391 -0
  38. package/bundle/components/ModalWindow.md +427 -237
  39. package/bundle/components/MonthYearField.md +145 -109
  40. package/bundle/components/Notification.md +625 -155
  41. package/bundle/components/Overlay.md +271 -170
  42. package/bundle/components/Page.md +284 -186
  43. package/bundle/components/Pagination.md +206 -229
  44. package/bundle/components/Portal.md +131 -67
  45. package/bundle/components/Row.md +171 -69
  46. package/bundle/components/Text.md +320 -0
  47. package/bundle/components/TextField.md +364 -97
  48. package/bundle/components/Title.md +296 -0
  49. package/bundle/guides/codestyle.md +38 -11
  50. package/bundle/guides/commits.md +119 -26
  51. package/bundle/guides/context.md +34 -30
  52. package/bundle/guides/stories.md +110 -40
  53. package/bundle/guides/template.md +3 -7
  54. package/bundle/guides/tests.md +48 -2
  55. package/bundle/manifest.json +275 -30
  56. package/bundle/release-notes/1.26.0.mdx +33 -2
  57. package/bundle/release-notes/1.27.0.mdx +43 -0
  58. package/bundle/release-notes/1.28.0.mdx +25 -0
  59. package/bundle/release-notes/1.29.0.mdx +47 -0
  60. package/bundle/release-notes/1.30.0.mdx +56 -0
  61. package/bundle/release-notes/1.31.0.mdx +52 -0
  62. package/bundle/release-notes/1.32.0.mdx +35 -0
  63. package/bundle/release-notes/1.33.0.mdx +61 -0
  64. package/bundle/release-notes/1.34.0.mdx +35 -0
  65. package/bundle/release-notes/1.35.0.mdx +21 -0
  66. package/bundle/release-notes/1.35.1.mdx +11 -0
  67. package/bundle/release-notes/1.36.0.mdx +21 -0
  68. package/bundle/release-notes/1.37.0.mdx +71 -0
  69. package/bundle/release-notes/1.38.0.mdx +36 -0
  70. package/bundle/release-notes/1.39.0.mdx +66 -0
  71. package/bundle/release-notes/1.40.0.mdx +27 -0
  72. package/bundle/release-notes/1.41.0.mdx +7 -0
  73. package/package.json +1 -1
@@ -1,11 +1,9 @@
1
1
  ---
2
2
  component: ListMaster
3
- category: Data Display
4
- figma-node: ""
5
- figma-file: ""
6
- related: [List, ChipGroup, Checkbox]
3
+ category: List
4
+ related: [ListMasterHeader, ListMasterFooter, List, ChipGroup]
7
5
  tokens: []
8
- stories: ""
6
+ stories: stories/ListMaster/ListMaster.stories.tsx
9
7
  version: "1.0"
10
8
  ---
11
9
 
@@ -13,192 +11,914 @@ version: "1.0"
13
11
 
14
12
  ## Назначение
15
13
 
16
- Тонкий layout-контейнер для экрана "список + фильтры + контролы выбора":
17
- оборачивает Header (sticky, со sticky-компенсацией scroll'а), Body (контент),
18
- ChipGroup (горизонтальные фильтры-чипы), Footer (sticky), SelectionControls
19
- (пакетные действия над выбранными элементами). Всё через compound-api
20
- `ListMaster.Header` / `.Body` / `.Footer` / `.ChipGroup` / `.SelectionControls`.
14
+ Compound-компонент, оборачивающий список и фильтры в единый layout. Объединяет
15
+ sticky-шапку (selection-controls), тело со списком и `ChipGroup`-фильтрами,
16
+ sticky-футер с агрегатами и действиями. Сам по себе — простой `<div>`-контейнер;
17
+ смысл придают статические подкомпоненты.
21
18
 
22
- Используй когда: нужен стандартный экран списка с фильтрами сверху и
23
- sticky-действиями снизу. Для самого списка — отдельно `List` / `Table`.
24
-
25
- ---
26
-
27
- ## Файловая структура
28
-
29
- ```text
30
- src/components/ListMaster/
31
- ├── ListMaster.tsx # <div>, compound через Object.assign
32
- ├── index.ts # Barrel
33
- ├── components/
34
- │ ├── ListMasterHeader.tsx # sticky + scroll-компенсация при mount/unmount
35
- │ ├── ListMasterBody.tsx # прозрачный <div>-wrapper
36
- │ ├── ListMasterFooter.tsx # sticky
37
- │ ├── ListMasterFooterControls.tsx
38
- │ ├── ListMasterFooterDescription.tsx
39
- │ ├── ListMasterChipGroup.tsx # обёртка над <ChipGroup oneLine>
40
- │ └── SelectionControls.tsx # блок пакетных действий
41
- └── styles/
42
- ├── ListMasterHeader.module.less
43
- ├── ListMasterFooter.module.less
44
- ├── ListMasterChipGroup.module.less
45
- └── SelectionControls.module.less
46
- ```
47
-
48
- ---
49
-
50
- ## Compound-структура
51
-
52
- ```tsx
53
- <ListMaster>
54
- <ListMaster.Header sticky>
55
- {/* Заголовок страницы, поиск, крупные фильтры */}
56
- </ListMaster.Header>
57
-
58
- <ListMaster.ChipGroup>
59
- <Chip>Фильтр 1</Chip>
60
- <Chip>Фильтр 2</Chip>
61
- </ListMaster.ChipGroup>
62
-
63
- <ListMaster.SelectionControls>
64
- {/* Кнопки массовых действий: "Удалить", "Экспортировать" */}
65
- </ListMaster.SelectionControls>
66
-
67
- <ListMaster.Body>
68
- <List>{/* ListItem'ы */}</List>
69
- </ListMaster.Body>
70
-
71
- <ListMaster.Footer sticky>
72
- <ListMaster.FooterDescription>{/* "Показано 10 из 100" */}</ListMaster.FooterDescription>
73
- <ListMaster.FooterControls>{/* Pagination */}</ListMaster.FooterControls>
74
- </ListMaster.Footer>
75
- </ListMaster>
76
- ```
19
+ Используй `ListMaster` когда: страница со списком, где нужны фильтры, выбор
20
+ элементов и действия по выбранному.
77
21
 
78
22
  ---
79
23
 
80
24
  ## Варианты и props
81
25
 
82
- ### ListMaster — `IListMasterProps extends HTMLAttributes<HTMLDivElement>`
83
-
84
- Без собственных props'ов. Просто `<div>` + `data-tx` + compound-подкомпоненты.
26
+ ### Опциональные props (`ListMaster`)
85
27
 
86
- ### ListMaster.Header — `IListMasterHeaderProps`
87
-
88
- | Prop | Тип | Default | Описание |
28
+ | Prop | Тип | По умолчанию | Описание |
89
29
  |---|---|---|---|
90
- | `sticky` | `boolean` | `true` | Добавляет класс `.sticky` (CSS `position: sticky`). |
91
-
92
- **Scroll-компенсация при mount/unmount** — см. ниже.
30
+ | `children` | `React.ReactNode` | — | Композиция из статических подкомпонентов |
31
+ | `...HTMLDivAttributes` | — | — | Все стандартные атрибуты `<div>` |
93
32
 
94
- ### ListMaster.Footer — `IListMasterFooterProps`
33
+ ### Статические подкомпоненты (compound API)
95
34
 
96
- | Prop | Тип | Default | Описание |
97
- |---|---|---|---|
98
- | `sticky` | `boolean` | `true` | Аналогично Header. Но без scroll-компенсации. |
99
-
100
- ### ListMaster.FooterControls / FooterDescription
101
-
102
- Простые `<div>`-слоты с семантическими именами для Footer.
103
-
104
- ### ListMaster.ChipGroup
105
-
106
- Обёртка над `<ChipGroup oneLine>` (из компонента `ChipGroup`) с доп. стилем (horizontal padding). **`oneLine` зашит** — нельзя отключить через props.
107
-
108
- ### ListMaster.Body
109
-
110
- Прозрачный forwardRef-wrapper, без стилей.
111
-
112
- ### ListMaster.SelectionControls
113
-
114
- `<div>` с классом `.selectionControls`. Рендерится, когда нужно показать действия над выбранными элементами (например, по флагу контроллера).
35
+ | Имя | Назначение | Отдельный AI.md |
36
+ |---|---|---|
37
+ | `ListMaster.Header` | Sticky-шапка (обычно показывается при выделении ≥1 элемента) | ✅ `ListMasterHeader-ai.md` |
38
+ | `ListMaster.Body` | Основная область с фильтрами и списком | — (тривиальная обёртка) |
39
+ | `ListMaster.ChipGroup` | Горизонтальная группа `Chip`-фильтров с фиксированным `oneLine` и отступами | — (описано ниже) |
40
+ | `ListMaster.Footer` | Sticky-футер с агрегатами и действиями | ✅ `ListMasterFooter-ai.md` |
41
+ | `ListMaster.FooterDescription` | Текстовая часть футера (например, «Сумма: 1 220 000 RUB») | — (тривиальная обёртка) |
42
+ | `ListMaster.FooterControls` | Контейнер для кнопок в футере | — (тривиальная обёртка) |
43
+ | `ListMaster.SelectionControls` | Flex-контейнер с `justify-content: space-between` для управления выделением (обычно: «Выбрать все» / «Выбрано: N» / «Сбросить») | — (описано ниже) |
115
44
 
116
45
  ---
117
46
 
118
- ## Ключевые особенности реализации
119
-
120
- ### Scroll-компенсация в `ListMasterHeader`
121
-
122
- ```tsx
123
- useEffect(() => {
124
- containerHeightRef.current = containerRef.current.getBoundingClientRect().height;
125
- window.scrollTo({ top: window.scrollY + containerHeightRef.current });
126
- return () => window.scrollTo({ top: Math.max(window.scrollY - containerHeightRef.current, 0) });
127
- }, []);
128
- ```
129
-
130
- - **При монтировании Header** (который обычно появляется при выборе элементов — `SelectionControls` mode) — страница "проскроллена" на высоту header'а, чтобы чекбоксы не "прыгнули" вниз.
131
- - **При размонтировании** — обратный scroll.
132
- - **Критичный side effect**: меняет `window.scrollY`. Если Header рендерится и пропадает часто — поведение видно. Этот UX-паттерн специфичен для случаев "выбрал строку → появился контрол-бар".
47
+ ## Инварианты
133
48
 
134
- ### `data-tx` на нескольких слотах
49
+ - **`forwardRef`** — обязателен на `ListMaster`. Реализован через `Object.assign(forwardRef, { Body, Header, ... })`. Не убирать.
50
+ - **Статические свойства** (`Body`, `Header`, `ChipGroup`, `Footer`, `FooterControls`, `FooterDescription`, `SelectionControls`) — часть публичного API. Не переименовывать без подтверждения.
51
+ - **Все компоненты семейства `ListMaster*`** также экспортируются из barrel `index.ts` отдельно (можно использовать как `import { ListMasterHeader }`, так и `<ListMaster.Header>`). Сохраняй оба варианта.
52
+ - **Корневой DOM** — `<div>`. Не менять, ref-target — этот div.
135
53
 
136
- `ListMaster`, `ListMasterBody`, `SelectionControls` имеют `data-tx={process.env.npm_package_version}`. Header/Footer/ChipGroup — **нет**. Неконсистентно, но это исторический паттерн — не добавляй/убирай без согласования.
54
+ ---
137
55
 
138
- ### `ListMasterChipGroup` зашивает `oneLine`
56
+ ## Связанные компоненты
139
57
 
140
- ```tsx
141
- <ChipGroup className={...} oneLine {...rest} ref={ref}>
142
- ```
58
+ ### Описаны здесь (тривиальные обёртки без отдельного AI.md)
143
59
 
144
- Если потребителю нужен многострочный ChipGroup в ListMaster — использовать `<ChipGroup>` напрямую, не через `.ChipGroup` слот.
60
+ - **`ListMasterBody`** (`ListMaster.Body`) — `<div>`-контейнер для основной области; ничего кроме `className + ...rest` не делает. Семантика — «между Header и Footer».
61
+ - **`ListMasterChipGroup`** (`ListMaster.ChipGroup`) — обёртка над `<ChipGroup>` с фиксированным `oneLine={true}` и горизонтальным padding `0 16px`. Используется для горизонтальной полосы фильтров с прокруткой.
62
+ - **`ListMasterFooterDescription`** (`ListMaster.FooterDescription`) — `<div>` с `padding-right: 16px`. Левая текстовая часть футера.
63
+ - **`ListMasterFooterControls`** (`ListMaster.FooterControls`) — `<div>` с `white-space: nowrap`. Правая часть футера для кнопок.
64
+ - **`SelectionControls`** (`ListMaster.SelectionControls`) — `<div>` с `display: flex; justify-content: space-between; align-items: center`. Layout для controls внутри `ListMaster.Header`.
145
65
 
146
- ### Compound через `FC & { ...slots }` + присваивания
66
+ ### Имеют отдельный AI.md
147
67
 
148
- ```tsx
149
- export interface IListMasterFC extends FC<IListMasterProps> { Body, ChipGroup, ... }
150
- export const ListMaster: IListMasterFC = (...) => <div>...</div>;
151
- ListMaster.Body = ListMasterBody;
152
- // ...
153
- ```
68
+ - **`ListMasterHeader`** — sticky-шапка с компенсацией высоты через `window.scrollTo` (см. `ListMasterHeader-ai.md`).
69
+ - **`ListMasterFooter`** — sticky-футер (см. `ListMasterFooter-ai.md`).
154
70
 
155
- **Не `Object.assign`** — явное присваивание после определения. Паттерн-вариант, используй тот же при добавлении слотов.
71
+ ### Внешние
156
72
 
157
- ---
158
-
159
- ## Инварианты
160
-
161
- - **`sticky=true` по умолчанию** на Header/Footer — публичный контракт.
162
- - **Scroll-компенсация в Header** — UX-решение, не удалять без обсуждения (ломает visual continuity в scenario "выбор элементов").
163
- - **`ListMasterChipGroup` зашитый `oneLine`** — контракт. Для многострочного — потребитель использует `ChipGroup` напрямую.
164
- - **ForwardRef** на всех слотах.
165
- - **Compound: `ListMaster.{Body,ChipGroup,Footer,FooterControls,FooterDescription,Header,SelectionControls}`** — публичный API.
166
- - **Отсутствуют собственные токены** — визуал наследуется от `ChipGroup` и общих CSS-переменных.
167
- - **Нет stories/тестов** — compound тонкий, покрытие лежит на демо-приложении. При добавлении новых слотов **обязательно** написать story.
73
+ - **`List`** — обычно содержится внутри `ListMaster.Body`.
74
+ - **`ChipGroup`** — используется внутри `ListMasterChipGroup` как реализация.
168
75
 
169
76
  ---
170
77
 
171
- ## Accessibility
172
-
173
- - `<div>`-контейнеры, без ARIA-ролей. Семантика — на совести потребителя (добавь `role="region"` + `aria-label` через `...rest` для скринридеров).
174
- - Sticky Header не ломает фокусируемость (CSS, не portal).
175
- - **Scroll-переход в Header при mount** — может сбивать скринридер на слабых устройствах (меняется позиция scroll'а). Не отключается через prop.
176
-
177
- ---
78
+ ## Stories
178
79
 
179
- ## Дизайн-токены
80
+ Основные истории: `stories/ListMaster/ListMaster.stories.tsx`
81
+ Файлы примеров: `stories/ListMaster/examples/`
180
82
 
181
- Нет собственных.
83
+ | Story | Example file | Что демонстрирует |
84
+ |---|---|---|
85
+ | `Default` | `Default.tsx` | Production-like пример: TabsLine + `ChipGroup`-фильтры + selectable `ListTableItem` + sticky Header при выделении + sticky Footer с суммой и действиями + LightBox с расширенными фильтрами + пустое состояние |
182
86
 
183
87
  ---
184
88
 
185
- ## Связанные компоненты
89
+ ## История изменений
186
90
 
187
- - `List` / `Table` / `TreeView` — то, что обычно внутри `<ListMaster.Body>`.
188
- - `ChipGroup` — используется в `.ChipGroup`.
189
- - `Checkbox` — часто в `.SelectionControls`.
190
- - `Pagination` — типично в `.FooterControls`.
91
+ | Дата | Изменение |
92
+ |---|---|
93
+ | 2026-04-29 | Создан документ. Добавлен `forwardRef` на `ListMaster` (breaking — изменён TS-тип, удалён `IListMasterFC`). Исправлен `displayName` у `ListMasterChipGroup`. Экспортирован `IListMasterChipGroupProps`. Добавлены unit-тесты. |
191
94
 
192
95
  ---
193
96
 
194
- ## Stories
97
+ ## Примеры использования
195
98
 
196
- Нет. Кандидат на добавление при следующей работе с компонентом.
99
+ ### Default
197
100
 
198
- ---
101
+ `stories/ListMaster/examples/Default.tsx`
199
102
 
200
- ## История изменений
103
+ ```tsx
104
+ import React from "react";
105
+ import {
106
+ Button,
107
+ ButtonDropdown,
108
+ Checkbox,
109
+ CheckboxYGroup,
110
+ ChipDatePicker,
111
+ ChipGroup,
112
+ ChipMultiselect,
113
+ ChipOptions,
114
+ ChipSort,
115
+ ChipSuggest,
116
+ EBodyPageType,
117
+ EButtonDotsTheme,
118
+ EButtonTheme,
119
+ EComponentSize,
120
+ EFontType,
121
+ EFontWeightText,
122
+ EFooterPageType,
123
+ EFormFieldStatus,
124
+ EHeaderPageType,
125
+ EMarkerStatus,
126
+ ETextSize,
127
+ ETitleSize,
128
+ Gap,
129
+ ISelectFieldOption,
130
+ ISuggestFieldOption,
131
+ LightBox,
132
+ List,
133
+ ListEmptyState,
134
+ ListItemControlsButton,
135
+ ListItemControlsButtonDropdown,
136
+ ListTableItem,
137
+ ListMaster,
138
+ MarkerStatus,
139
+ MultiselectField,
140
+ Page,
141
+ TabsLine,
142
+ Text,
143
+ Title,
144
+ } from "@sberbusiness/triplex-next";
145
+ import {
146
+ AttachmentStrokeSrvIcon20,
147
+ DotshorizontalStrokeSrvIcon20,
148
+ EmptytableSysIcon96,
149
+ } from "@sberbusiness/icons-next";
150
+
151
+ type TTabId = "all" | "draft" | "sign" | "executed" | "rejected";
152
+ type TItemStatus = "draft" | "signed" | "executed" | "rejected";
153
+
154
+ const tabsLine: Array<{ id: TTabId; label: string; showNotificationIcon?: boolean }> = [
155
+ { id: "all", label: "Все" },
156
+ { id: "draft", label: "Черновики", showNotificationIcon: true },
157
+ { id: "sign", label: "На подпись и отправку" },
158
+ { id: "executed", label: "Исполненные" },
159
+ { id: "rejected", label: "Отклоненные" },
160
+ ];
161
+
162
+ const chipSortOptions: ISelectFieldOption[] = [
163
+ { id: "list-master-chip-sort-1", label: "По возрастанию суммы", value: "asc" },
164
+ { id: "list-master-chip-sort-2", label: "По убыванию суммы", value: "desc" },
165
+ ];
166
+
167
+ const allStatuses: TItemStatus[] = ["draft", "signed", "executed", "rejected"];
168
+
169
+ const statusOptions: Array<{ id: string; label: string; value: TItemStatus }> = [
170
+ { id: "status-draft", label: "Черновики", value: "draft" },
171
+ { id: "status-signed", label: "На подпись", value: "signed" },
172
+ { id: "status-executed", label: "Исполненные", value: "executed" },
173
+ { id: "status-rejected", label: "Отклоненные", value: "rejected" },
174
+ ];
175
+
176
+ const statusToMarker: Record<TItemStatus, EMarkerStatus> = {
177
+ draft: EMarkerStatus.WAITING,
178
+ signed: EMarkerStatus.SUCCESS,
179
+ executed: EMarkerStatus.SUCCESS,
180
+ rejected: EMarkerStatus.ERROR,
181
+ };
182
+
183
+ const initialAccountOptions: ISuggestFieldOption[] = [
184
+ { id: "acc-1", label: "Счет 40702 810..." },
185
+ { id: "acc-2", label: "Счет 40702 999..." },
186
+ ];
187
+
188
+ const initialCounterpartyOptions: ISuggestFieldOption[] = [
189
+ { id: "cp-1", label: "ООО Голубая Роза" },
190
+ { id: "cp-2", label: "ООО Лунная Лилия" },
191
+ ];
192
+
193
+ interface IListItemData {
194
+ id: string;
195
+ amount: number;
196
+ title: string;
197
+ description: string;
198
+ numberAndDate: string;
199
+ paymentDate: string;
200
+ status: TItemStatus;
201
+ statusText: string;
202
+ accountId: string;
203
+ counterpartyId: string;
204
+ }
205
+
206
+ const listItemsData: IListItemData[] = [
207
+ {
208
+ id: "1",
209
+ amount: 1_220_000_000,
210
+ title: "№1 ООО Голубая Роза Голубая",
211
+ description: "НДС не облагается",
212
+ numberAndDate: "40702 810 2 0527 5000000 от 09.04.24",
213
+ paymentDate: "20240409",
214
+ status: "draft",
215
+ statusText: "Черновик",
216
+ accountId: "acc-1",
217
+ counterpartyId: "cp-1",
218
+ },
219
+ {
220
+ id: "2",
221
+ amount: 85_500_000,
222
+ title: "№2 ООО Лунная Лилия",
223
+ description: "НДС не облагается",
224
+ numberAndDate: "40702 810 2 0527 5000001 от 21.05.24",
225
+ paymentDate: "20240521",
226
+ status: "signed",
227
+ statusText: "На подпись",
228
+ accountId: "acc-1",
229
+ counterpartyId: "cp-2",
230
+ },
231
+ {
232
+ id: "3",
233
+ amount: 3_300_000,
234
+ title: "№3 ООО Голубая Роза",
235
+ description: "НДС не облагается",
236
+ numberAndDate: "40702 810 2 0527 5000002 от 02.06.24",
237
+ paymentDate: "20240602",
238
+ status: "executed",
239
+ statusText: "Исполнено",
240
+ accountId: "acc-2",
241
+ counterpartyId: "cp-1",
242
+ },
243
+ {
244
+ id: "4",
245
+ amount: 12_700_000,
246
+ title: "№4 ООО Лунная Лилия",
247
+ description: "НДС не облагается",
248
+ numberAndDate: "40702 999 1 0527 5000003 от 10.07.24",
249
+ paymentDate: "20240710",
250
+ status: "rejected",
251
+ statusText: "Отклонено",
252
+ accountId: "acc-2",
253
+ counterpartyId: "cp-2",
254
+ },
255
+ {
256
+ id: "5",
257
+ amount: 12_700_000,
258
+ title: "№4 ООО Лунная Лилия",
259
+ description: "НДС не облагается",
260
+ numberAndDate: "40702 999 1 0527 5000003 от 10.07.24",
261
+ paymentDate: "20240710",
262
+ status: "rejected",
263
+ statusText: "Отклонено",
264
+ accountId: "acc-2",
265
+ counterpartyId: "cp-2",
266
+ },
267
+ {
268
+ id: "6",
269
+ amount: 12_700_000,
270
+ title: "№4 ООО Лунная Лилия",
271
+ description: "НДС не облагается",
272
+ numberAndDate: "40702 999 1 0527 5000003 от 10.07.24",
273
+ paymentDate: "20240710",
274
+ status: "rejected",
275
+ statusText: "Отклонено",
276
+ accountId: "acc-2",
277
+ counterpartyId: "cp-2",
278
+ },
279
+ {
280
+ id: "7",
281
+ amount: 12_700_000,
282
+ title: "№4 ООО Лунная Лилия",
283
+ description: "НДС не облагается",
284
+ numberAndDate: "40702 999 1 0527 5000003 от 10.07.24",
285
+ paymentDate: "20240710",
286
+ status: "rejected",
287
+ statusText: "Отклонено",
288
+ accountId: "acc-2",
289
+ counterpartyId: "cp-2",
290
+ },
291
+ ];
292
+
293
+ export const Default = () => {
294
+ const [chipSortValue, setChipSortValue] = React.useState<ISelectFieldOption>(chipSortOptions[0]);
295
+ const [selectedListItemIds, setSelectedListItemIds] = React.useState<string[]>([]);
296
+ const [selectedTabLineId, setSelectedTabLineId] = React.useState<TTabId>("all");
297
+
298
+ const [accountValue, setAccountValue] = React.useState<ISuggestFieldOption | undefined>(undefined);
299
+ const [accountOptions, setAccountOptions] = React.useState<ISuggestFieldOption[]>([]);
300
+ const [accountTooltipOpen, setAccountTooltipOpen] = React.useState(false);
301
+ const accountInitialOptionsRef = React.useRef<ISuggestFieldOption[]>(initialAccountOptions);
302
+
303
+ const [counterpartyValue, setCounterpartyValue] = React.useState<ISuggestFieldOption | undefined>(undefined);
304
+ const [counterpartyOptions, setCounterpartyOptions] = React.useState<ISuggestFieldOption[]>([]);
305
+ const [counterpartyTooltipOpen, setCounterpartyTooltipOpen] = React.useState(false);
306
+ const counterpartyInitialOptionsRef = React.useRef<ISuggestFieldOption[]>(initialCounterpartyOptions);
307
+
308
+ const [multiselectStatusValues, setMultiselectStatusValues] = React.useState<TItemStatus[]>(allStatuses);
309
+
310
+ const [dateFrom, setDateFrom] = React.useState("");
311
+ const [dateTo, setDateTo] = React.useState("");
312
+
313
+ const [openLightBoxOptions, setOpenLightBoxOptions] = React.useState(false);
314
+
315
+ const clearFilters = () => {
316
+ setAccountValue(undefined);
317
+ setCounterpartyValue(undefined);
318
+ setMultiselectStatusValues(allStatuses);
319
+ setDateFrom("");
320
+ setDateTo("");
321
+ setSelectedListItemIds([]);
322
+ };
323
+
324
+ const changedFiltersCount =
325
+ (accountValue ? 1 : 0) +
326
+ (counterpartyValue ? 1 : 0) +
327
+ (multiselectStatusValues.length !== allStatuses.length ? 1 : 0) +
328
+ (dateFrom ? 1 : 0) +
329
+ (dateTo ? 1 : 0);
330
+
331
+ const filteredItems = React.useMemo(() => {
332
+ let next = [...listItemsData];
333
+
334
+ if (selectedTabLineId !== "all") {
335
+ const map: Record<Exclude<TTabId, "all">, TItemStatus> = {
336
+ draft: "draft",
337
+ sign: "signed",
338
+ executed: "executed",
339
+ rejected: "rejected",
340
+ };
341
+ next = next.filter((i) => i.status === map[selectedTabLineId as Exclude<TTabId, "all">]);
342
+ }
343
+
344
+ if (multiselectStatusValues.length !== allStatuses.length) {
345
+ next = next.filter((i) => multiselectStatusValues.includes(i.status));
346
+ }
347
+
348
+ if (accountValue) next = next.filter((i) => i.accountId === accountValue.id);
349
+ if (counterpartyValue) next = next.filter((i) => i.counterpartyId === counterpartyValue.id);
350
+
351
+ if (dateFrom) {
352
+ next = next.filter((i) => i.paymentDate >= dateFrom);
353
+ }
354
+
355
+ if (dateTo) {
356
+ next = next.filter((i) => i.paymentDate <= dateTo);
357
+ }
358
+
359
+ return next;
360
+ }, [dateFrom, dateTo, multiselectStatusValues, accountValue, counterpartyValue, selectedTabLineId]);
361
+
362
+ const sortedItems = React.useMemo(() => {
363
+ const next = [...filteredItems];
364
+ next.sort((a, b) => (chipSortValue.value === "desc" ? b.amount - a.amount : a.amount - b.amount));
365
+ return next;
366
+ }, [filteredItems, chipSortValue]);
367
+
368
+ const sumSelected = React.useMemo(() => {
369
+ return listItemsData
370
+ .filter((item) => selectedListItemIds.includes(item.id))
371
+ .reduce((acc, item) => acc + item.amount, 0);
372
+ }, [selectedListItemIds]);
373
+
374
+ const handleSelectListItem = (id: string, selected: boolean) => {
375
+ setSelectedListItemIds((prev) => (selected ? [...prev, id] : prev.filter((itemId) => itemId !== id)));
376
+ };
377
+
378
+ const selectAllListItems = () => {
379
+ setSelectedListItemIds(sortedItems.map((i) => i.id));
380
+ };
381
+
382
+ const clearSelected = () => setSelectedListItemIds([]);
383
+
384
+ const itemActions = [
385
+ { id: "action-1", label: "Действие 1", onSelect: () => alert("Действие 1") },
386
+ { id: "action-2", label: "Действие 2", onSelect: () => alert("Действие 2") },
387
+ ];
388
+
389
+ const options = [
390
+ {
391
+ id: "button-dropdown-card-with-selectable-option-1",
392
+ label: "Текст пункта меню 1",
393
+ onSelect: () => alert("Выбран пункт меню 1."),
394
+ },
395
+ {
396
+ id: "button-dropdown-card-with-selectable-option-2",
397
+ label: "Текст пункта меню 2",
398
+ onSelect: () => alert("Выбран пункт меню 2."),
399
+ },
400
+ {
401
+ id: "button-dropdown-card-with-selectable-option-3",
402
+ label: "Текст пункта меню 3",
403
+ onSelect: () => alert("Выбран пункт меню 3."),
404
+ },
405
+ ];
406
+
407
+ return (
408
+ <div>
409
+ <ListMaster>
410
+ {selectedListItemIds.length ? (
411
+ <ListMaster.Header>
412
+ <ListMaster.SelectionControls>
413
+ <Button theme={EButtonTheme.LINK} size={EComponentSize.MD} onClick={selectAllListItems}>
414
+ Выбрать все
415
+ </Button>
416
+
417
+ <Text size={ETextSize.B3} weight={EFontWeightText.SEMIBOLD}>
418
+ Выбрано: {selectedListItemIds.length}
419
+ </Text>
420
+
421
+ <Button theme={EButtonTheme.LINK} size={EComponentSize.MD} onClick={clearSelected}>
422
+ Сбросить все
423
+ </Button>
424
+ </ListMaster.SelectionControls>
425
+ </ListMaster.Header>
426
+ ) : null}
427
+
428
+ <ListMaster.Body>
429
+ <Gap size={16} />
430
+
431
+ <TabsLine
432
+ tabs={tabsLine}
433
+ selectedId={selectedTabLineId}
434
+ onChangeTab={(tabId) => {
435
+ setSelectedTabLineId(tabId as TTabId);
436
+ setSelectedListItemIds([]);
437
+ }}
438
+ paddingX={16}
439
+ withSeparator
440
+ />
441
+
442
+ <Gap size={16} />
443
+
444
+ <ListMaster.ChipGroup>
445
+ <ChipSort
446
+ label="Сортировка"
447
+ defaultValue={chipSortOptions[0]}
448
+ value={chipSortValue}
449
+ options={chipSortOptions}
450
+ onChange={setChipSortValue}
451
+ size={EComponentSize.MD}
452
+ />
453
+
454
+ <ChipOptions
455
+ selected={changedFiltersCount > 0}
456
+ clearSelected={clearFilters}
457
+ onClick={() => setOpenLightBoxOptions(true)}
458
+ size={EComponentSize.MD}
459
+ >
460
+ {changedFiltersCount > 0 ? changedFiltersCount : undefined}
461
+ </ChipOptions>
462
+
463
+ <ChipSuggest
464
+ size={EComponentSize.MD}
465
+ label="Счет"
466
+ placeholder="Выберите счет"
467
+ noOptionsText="Ничего не найдено"
468
+ value={accountValue}
469
+ options={accountOptions}
470
+ tooltipOpen={accountTooltipOpen}
471
+ onSelect={(value) => {
472
+ setAccountValue(value);
473
+ setSelectedListItemIds([]);
474
+ }}
475
+ onFilter={(input) => {
476
+ if (!input) {
477
+ const initial = accountInitialOptionsRef.current;
478
+ setAccountOptions(initial);
479
+ setAccountTooltipOpen(false);
480
+ return;
481
+ }
482
+ const filtered = accountInitialOptionsRef.current.filter((o) =>
483
+ o.label.toLowerCase().includes(input.toLowerCase()),
484
+ );
485
+ setAccountOptions(filtered);
486
+ setAccountTooltipOpen(filtered.length === 0);
487
+ }}
488
+ targetProps={{
489
+ clearSelected: () => {
490
+ setAccountValue(undefined);
491
+ setSelectedListItemIds([]);
492
+ },
493
+ }}
494
+ dropdownProps={{
495
+ onOpen: () => {
496
+ setAccountOptions(accountInitialOptionsRef.current);
497
+ setAccountTooltipOpen(false);
498
+ },
499
+ }}
500
+ />
501
+
502
+ <ChipSuggest
503
+ size={EComponentSize.MD}
504
+ label="Контрагент"
505
+ placeholder="Выберите контрагента"
506
+ noOptionsText="Ничего не найдено"
507
+ value={counterpartyValue}
508
+ options={counterpartyOptions}
509
+ tooltipOpen={counterpartyTooltipOpen}
510
+ onSelect={(value) => {
511
+ setCounterpartyValue(value);
512
+ setSelectedListItemIds([]);
513
+ }}
514
+ onFilter={(input) => {
515
+ if (!input) {
516
+ const initial = counterpartyInitialOptionsRef.current;
517
+ setCounterpartyOptions(initial);
518
+ setCounterpartyTooltipOpen(false);
519
+ return;
520
+ }
521
+ const filtered = counterpartyInitialOptionsRef.current.filter((o) =>
522
+ o.label.toLowerCase().includes(input.toLowerCase()),
523
+ );
524
+ setCounterpartyOptions(filtered);
525
+ setCounterpartyTooltipOpen(filtered.length === 0);
526
+ }}
527
+ targetProps={{
528
+ clearSelected: () => {
529
+ setCounterpartyValue(undefined);
530
+ setSelectedListItemIds([]);
531
+ },
532
+ }}
533
+ dropdownProps={{
534
+ onOpen: () => {
535
+ setCounterpartyOptions(counterpartyInitialOptionsRef.current);
536
+ setCounterpartyTooltipOpen(false);
537
+ },
538
+ }}
539
+ />
540
+
541
+ <ChipMultiselect
542
+ clearSelected={() => {
543
+ setMultiselectStatusValues(allStatuses);
544
+ setSelectedListItemIds([]);
545
+ }}
546
+ selected={multiselectStatusValues.length !== allStatuses.length}
547
+ label="Статус"
548
+ displayedValue={`${multiselectStatusValues.length}`}
549
+ size={EComponentSize.MD}
550
+ >
551
+ {({ opened, setOpened, targetRef, dropdownRef }) => (
552
+ <MultiselectField.Dropdown
553
+ opened={opened}
554
+ setOpened={setOpened}
555
+ targetRef={targetRef}
556
+ ref={dropdownRef}
557
+ >
558
+ <MultiselectField.Dropdown.Content>
559
+ <div
560
+ style={{
561
+ display: "flex",
562
+ flexDirection: "column",
563
+ gap: 8,
564
+ }}
565
+ >
566
+ {statusOptions.map((opt) => {
567
+ const checked = multiselectStatusValues.includes(opt.value);
568
+ return (
569
+ <CheckboxYGroup key={opt.id}>
570
+ <Checkbox
571
+ checked={checked}
572
+ onChange={(e) => {
573
+ const nextChecked = e.target.checked;
574
+ setMultiselectStatusValues((prev) => {
575
+ if (nextChecked) {
576
+ if (prev.includes(opt.value)) return prev;
577
+ return [...prev, opt.value];
578
+ }
579
+ return prev.filter((v) => v !== opt.value);
580
+ });
581
+ setSelectedListItemIds([]);
582
+ }}
583
+ >
584
+ {opt.label}
585
+ </Checkbox>
586
+ </CheckboxYGroup>
587
+ );
588
+ })}
589
+ </div>
590
+ </MultiselectField.Dropdown.Content>
591
+ </MultiselectField.Dropdown>
592
+ )}
593
+ </ChipMultiselect>
594
+
595
+ <ChipDatePicker
596
+ value={dateFrom}
597
+ label="Дата с"
598
+ onChange={(value) => {
599
+ setDateFrom(value);
600
+ setSelectedListItemIds([]);
601
+ }}
602
+ size={EComponentSize.MD}
603
+ status={EFormFieldStatus.DEFAULT}
604
+ />
605
+
606
+ <ChipDatePicker
607
+ value={dateTo}
608
+ label="Дата по"
609
+ onChange={(value) => {
610
+ setDateTo(value);
611
+ setSelectedListItemIds([]);
612
+ }}
613
+ size={EComponentSize.MD}
614
+ status={EFormFieldStatus.DEFAULT}
615
+ />
616
+ </ListMaster.ChipGroup>
617
+
618
+ <Gap size={12} />
619
+
620
+ {sortedItems.length ? (
621
+ <List>
622
+ {sortedItems.map((item) => (
623
+ <ListTableItem
624
+ key={item.id}
625
+ selected={selectedListItemIds.includes(item.id)}
626
+ onSelect={(sel) => handleSelectListItem(item.id, sel)}
627
+ onClickItem={() => console.log("click", item.id)}
628
+ controlButtons={
629
+ <>
630
+ <ListItemControlsButton
631
+ icon={<AttachmentStrokeSrvIcon20 paletteIndex={5} />}
632
+ >
633
+ Скачать
634
+ </ListItemControlsButton>
635
+
636
+ <ListItemControlsButtonDropdown
637
+ icon={<DotshorizontalStrokeSrvIcon20 paletteIndex={5} />}
638
+ options={itemActions}
639
+ >
640
+ Действия
641
+ </ListItemControlsButtonDropdown>
642
+ </>
643
+ }
644
+ >
645
+ <Text size={ETextSize.B2} weight={EFontWeightText.SEMIBOLD}>
646
+ {item.amount.toLocaleString("ru-RU")} RUB
647
+ </Text>
648
+ <Text size={ETextSize.B3} tag="div">
649
+ {item.title}
650
+ </Text>
651
+ <Text size={ETextSize.B3} type={EFontType.SECONDARY} tag="div">
652
+ {item.description}
653
+ </Text>
654
+ <Text size={ETextSize.B3} type={EFontType.SECONDARY} tag="div">
655
+ {item.numberAndDate}
656
+ </Text>
657
+
658
+ <MarkerStatus status={statusToMarker[item.status]} size={EComponentSize.LG}>
659
+ {item.statusText}
660
+ </MarkerStatus>
661
+ </ListTableItem>
662
+ ))}
663
+ </List>
664
+ ) : (
665
+ <ListEmptyState>
666
+ <EmptytableSysIcon96 />
667
+
668
+ <Gap size={8} />
669
+
670
+ <Text size={ETextSize.B3}>Ничего не найдено. Попробуйте выбрать другие фильтры.</Text>
671
+
672
+ <Gap size={16} />
673
+
674
+ <Button theme={EButtonTheme.SECONDARY} size={EComponentSize.SM} onClick={clearFilters}>
675
+ Действие
676
+ </Button>
677
+
678
+ <Button theme={EButtonTheme.GENERAL} size={EComponentSize.SM} onClick={clearFilters}>
679
+ Сбросить фильтры
680
+ </Button>
681
+ </ListEmptyState>
682
+ )}
683
+ </ListMaster.Body>
684
+
685
+ {selectedListItemIds.length ? (
686
+ <ListMaster.Footer>
687
+ <ListMaster.FooterDescription>
688
+ <Text size={ETextSize.B3} weight={EFontWeightText.SEMIBOLD}>
689
+ Сумма:
690
+ <br />
691
+ {sumSelected.toLocaleString("ru-RU")} RUB
692
+ </Text>
693
+ </ListMaster.FooterDescription>
694
+
695
+ <ListMaster.FooterControls>
696
+ <ButtonDropdown
697
+ theme={EButtonDotsTheme.DOTS_SECONDARY}
698
+ size={EComponentSize.MD}
699
+ options={options}
700
+ />
701
+
702
+ <Button theme={EButtonTheme.GENERAL} size={EComponentSize.MD}>
703
+ Действие
704
+ </Button>
705
+ </ListMaster.FooterControls>
706
+ </ListMaster.Footer>
707
+ ) : null}
708
+ </ListMaster>
709
+
710
+ {openLightBoxOptions ? (
711
+ <LightBox>
712
+ <LightBox.Content>
713
+ <Page>
714
+ <Page.Header type={EHeaderPageType.FIRST}>
715
+ <Page.Header.Title>
716
+ <Page.Header.Title.Content>
717
+ <Title size={ETitleSize.H2} tag="div">
718
+ Фильтры: {changedFiltersCount}
719
+ </Title>
720
+ </Page.Header.Title.Content>
721
+ </Page.Header.Title>
722
+ </Page.Header>
723
+
724
+ <Page.Body type={EBodyPageType.FIRST}>
725
+ <Text size={ETextSize.B3}>Плательщик и Получатель</Text>
726
+
727
+ <Gap size={16} />
728
+
729
+ <ChipGroup>
730
+ <ChipSuggest
731
+ size={EComponentSize.MD}
732
+ label="Счет"
733
+ placeholder="Выберите счет"
734
+ noOptionsText="Ничего не найдено"
735
+ value={accountValue}
736
+ options={accountOptions}
737
+ tooltipOpen={accountTooltipOpen}
738
+ onSelect={(value) => {
739
+ setAccountValue(value);
740
+ setSelectedListItemIds([]);
741
+ }}
742
+ onFilter={(input) => {
743
+ if (!input) {
744
+ const initial = accountInitialOptionsRef.current;
745
+ setAccountOptions(initial);
746
+ setAccountTooltipOpen(false);
747
+ return;
748
+ }
749
+ const filtered = accountInitialOptionsRef.current.filter((o) =>
750
+ o.label.toLowerCase().includes(input.toLowerCase()),
751
+ );
752
+ setAccountOptions(filtered);
753
+ setAccountTooltipOpen(filtered.length === 0);
754
+ }}
755
+ targetProps={{
756
+ clearSelected: () => {
757
+ setAccountValue(undefined);
758
+ setSelectedListItemIds([]);
759
+ },
760
+ }}
761
+ dropdownProps={{
762
+ onOpen: () => {
763
+ setAccountOptions(accountInitialOptionsRef.current);
764
+ setAccountTooltipOpen(false);
765
+ },
766
+ }}
767
+ />
768
+
769
+ <ChipSuggest
770
+ size={EComponentSize.MD}
771
+ label="Контрагент"
772
+ placeholder="Выберите контрагента"
773
+ noOptionsText="Ничего не найдено"
774
+ value={counterpartyValue}
775
+ options={counterpartyOptions}
776
+ tooltipOpen={counterpartyTooltipOpen}
777
+ onSelect={(value) => {
778
+ setCounterpartyValue(value);
779
+ setSelectedListItemIds([]);
780
+ }}
781
+ onFilter={(input) => {
782
+ if (!input) {
783
+ const initial = counterpartyInitialOptionsRef.current;
784
+ setCounterpartyOptions(initial);
785
+ setCounterpartyTooltipOpen(false);
786
+ return;
787
+ }
788
+ const filtered = counterpartyInitialOptionsRef.current.filter((o) =>
789
+ o.label.toLowerCase().includes(input.toLowerCase()),
790
+ );
791
+ setCounterpartyOptions(filtered);
792
+ setCounterpartyTooltipOpen(filtered.length === 0);
793
+ }}
794
+ targetProps={{
795
+ clearSelected: () => {
796
+ setCounterpartyValue(undefined);
797
+ setSelectedListItemIds([]);
798
+ },
799
+ }}
800
+ dropdownProps={{
801
+ onOpen: () => {
802
+ setCounterpartyOptions(counterpartyInitialOptionsRef.current);
803
+ setCounterpartyTooltipOpen(false);
804
+ },
805
+ }}
806
+ />
807
+ </ChipGroup>
808
+
809
+ <Gap size={24} />
810
+
811
+ <Text size={ETextSize.B3}>Статус</Text>
812
+
813
+ <Gap size={16} />
814
+
815
+ <ChipMultiselect
816
+ clearSelected={() => {
817
+ setMultiselectStatusValues(allStatuses);
818
+ setSelectedListItemIds([]);
819
+ }}
820
+ selected={multiselectStatusValues.length !== allStatuses.length}
821
+ label="Статус"
822
+ displayedValue={`${multiselectStatusValues.length}`}
823
+ size={EComponentSize.MD}
824
+ >
825
+ {({ opened, setOpened, targetRef, dropdownRef }) => (
826
+ <MultiselectField.Dropdown
827
+ opened={opened}
828
+ setOpened={setOpened}
829
+ targetRef={targetRef}
830
+ ref={dropdownRef}
831
+ >
832
+ <MultiselectField.Dropdown.Content>
833
+ <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
834
+ {statusOptions.map((opt) => {
835
+ const checked = multiselectStatusValues.includes(opt.value);
836
+ return (
837
+ <Checkbox
838
+ key={opt.id}
839
+ checked={checked}
840
+ onChange={(e) => {
841
+ const nextChecked = e.target.checked;
842
+ setMultiselectStatusValues((prev) => {
843
+ if (nextChecked) {
844
+ if (prev.includes(opt.value)) return prev;
845
+ return [...prev, opt.value];
846
+ }
847
+ return prev.filter((v) => v !== opt.value);
848
+ });
849
+ setSelectedListItemIds([]);
850
+ }}
851
+ >
852
+ {opt.label}
853
+ </Checkbox>
854
+ );
855
+ })}
856
+ </div>
857
+ </MultiselectField.Dropdown.Content>
858
+ </MultiselectField.Dropdown>
859
+ )}
860
+ </ChipMultiselect>
861
+
862
+ <Gap size={24} />
863
+
864
+ <Text size={ETextSize.B3}>Дата документа</Text>
865
+
866
+ <Gap size={16} />
867
+
868
+ <ChipGroup>
869
+ <ChipDatePicker
870
+ value={dateFrom}
871
+ label="Дата с"
872
+ onChange={setDateFrom}
873
+ size={EComponentSize.MD}
874
+ status={EFormFieldStatus.DEFAULT}
875
+ />
876
+
877
+ <ChipDatePicker
878
+ value={dateTo}
879
+ label="Дата по"
880
+ onChange={setDateTo}
881
+ size={EComponentSize.MD}
882
+ status={EFormFieldStatus.DEFAULT}
883
+ />
884
+ </ChipGroup>
885
+ </Page.Body>
886
+
887
+ <Page.Footer type={EFooterPageType.FIRST}>
888
+ <Page.Footer.Description>
889
+ <Page.Footer.Description.Controls>
890
+ <Button
891
+ theme={EButtonTheme.SECONDARY}
892
+ size={EComponentSize.MD}
893
+ onClick={clearFilters}
894
+ >
895
+ Сбросить
896
+ </Button>
897
+
898
+ <Button
899
+ theme={EButtonTheme.GENERAL}
900
+ size={EComponentSize.MD}
901
+ onClick={() => setOpenLightBoxOptions(false)}
902
+ >
903
+ Применить
904
+ </Button>
905
+ </Page.Footer.Description.Controls>
906
+ </Page.Footer.Description>
907
+ </Page.Footer>
908
+ </Page>
909
+ </LightBox.Content>
910
+
911
+ <LightBox.Controls>
912
+ <LightBox.Controls.Close
913
+ title="Закрыть"
914
+ data-test-id="lightbox-close"
915
+ onClick={() => setOpenLightBoxOptions(false)}
916
+ />
917
+ </LightBox.Controls>
918
+ </LightBox>
919
+ ) : null}
920
+ </div>
921
+ );
922
+ };
923
+ ```
201
924
 
202
- | Дата | Изменение |
203
- |---|---|
204
- | 2026-04-18 | Создан документ |