@sberbusiness/triplex-next-mcp-server 1.45.0-beta → 1.48.0-beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/README.md +7 -7
  2. package/bundle/components/AmountField.md +222 -323
  3. package/bundle/components/Avatar.md +138 -146
  4. package/bundle/components/CardAction.md +615 -0
  5. package/bundle/components/CardStatic.md +402 -0
  6. package/bundle/components/CheckboxTree.md +157 -215
  7. package/bundle/components/CheckboxTreeExtended.md +7 -7
  8. package/bundle/components/Chip.md +7 -6
  9. package/bundle/components/ChipGroup.md +1 -1
  10. package/bundle/components/ChipMonthYearPicker.md +344 -0
  11. package/bundle/components/ChipMultiselect.md +1251 -0
  12. package/bundle/components/ChipOptions.md +329 -0
  13. package/bundle/components/ChipSort.md +333 -0
  14. package/bundle/components/ChipSuggest.md +588 -0
  15. package/bundle/components/CollapsibleTree.md +8 -4
  16. package/bundle/components/CollapsibleTreeExtended.md +8 -6
  17. package/bundle/components/Confirm.md +346 -281
  18. package/bundle/components/DateField.md +222 -303
  19. package/bundle/components/DatePickerExtended.md +3 -1
  20. package/bundle/components/DateRange.md +351 -210
  21. package/bundle/components/FooterPage.md +1 -0
  22. package/bundle/components/FormField.md +14 -5
  23. package/bundle/components/FormGroup.md +3 -2
  24. package/bundle/components/HelpBox.md +7 -1
  25. package/bundle/components/IslandAccordion.md +443 -152
  26. package/bundle/components/IslandWidget.md +622 -157
  27. package/bundle/components/LightBox.md +8 -0
  28. package/bundle/components/ListMaster.md +2 -2
  29. package/bundle/components/ListTableItem.md +6 -6
  30. package/bundle/components/MaskedField.md +2 -1
  31. package/bundle/components/MonthYearField.md +18 -5
  32. package/bundle/components/Slider.md +389 -97
  33. package/bundle/components/SliderRange.md +486 -105
  34. package/bundle/components/StatusTracker.md +386 -94
  35. package/bundle/components/Stepper.md +355 -102
  36. package/bundle/components/StepperExtended.md +633 -0
  37. package/bundle/components/SuggestField.md +1049 -96
  38. package/bundle/components/SwipeableArea.md +7 -7
  39. package/bundle/components/Tabs.md +265 -92
  40. package/bundle/components/TabsExtended.md +18 -18
  41. package/bundle/components/TabsLine.md +301 -127
  42. package/bundle/components/TagColor.md +187 -66
  43. package/bundle/components/TagGroup.md +184 -48
  44. package/bundle/components/TextareaField.md +278 -70
  45. package/bundle/components/TopOverlay.md +17 -1
  46. package/bundle/components/TreeView.md +16 -7
  47. package/bundle/components/TriggerClickOnKeyDownEvent.md +277 -0
  48. package/bundle/components/UploadZone.md +318 -0
  49. package/bundle/guides/codestyle.md +16 -1
  50. package/bundle/guides/commits.md +47 -1
  51. package/bundle/guides/tests.md +23 -2
  52. package/bundle/manifest.json +92 -24
  53. package/bundle/release-notes/1.46.0.mdx +59 -0
  54. package/bundle/release-notes/1.47.0.mdx +122 -0
  55. package/bundle/release-notes/1.48.0.mdx +68 -0
  56. package/bundle/release-notes/1.49.0.mdx +7 -0
  57. package/bundle/tokens.json +3 -3
  58. package/dist/index.js +220 -6
  59. package/dist/index.js.map +1 -1
  60. package/package.json +1 -1
@@ -1,10 +1,10 @@
1
1
  ---
2
2
  component: IslandWidget
3
3
  category: Layout
4
- figma-node: ""
5
- figma-file: ""
6
- related: [Island, IslandAccordion, ExpandAnimation, MediaWidth]
7
- tokens: []
4
+ related: [IslandAccordion, Island, ExpandAnimation, Text, Title]
5
+ tokens:
6
+ - IslandWidget.ExtraFooter_Background
7
+ - IslandWidget.ExtraFooter_Shadow
8
8
  stories: stories/IslandWidget/IslandWidget.stories.tsx
9
9
  version: "1.0"
10
10
  ---
@@ -13,221 +13,686 @@ version: "1.0"
13
13
 
14
14
  ## Назначение
15
15
 
16
- Виджет-карточка: как `Island` (TYPE_1, MD), но:
17
- - Принимает контент через **render-props** (`renderHeader`/`renderBody`/`renderFooter`) — не через children.
18
- - На **мобиле (≤ SM_MAX) автоматически сворачивает Body+Footer**, заголовок становится кликабельным, рядом появляется chevron.
19
- - Поддерживает необязательный **`ExtraFooter`** — дополнительный раздел, который открывается/закрывается управляемо (`open` prop), **снаружи** Island'а.
20
- - Оптимизирован под работу в группе (`IslandWidgetWrapper` — provider для `hasExtraFooter`).
16
+ Виджет — карточка на `Island`, собранная из шапки, тела и подвала, которые задаются
17
+ render-функциями. Отличие от голого `Island` — своя схема отступов по размеру и сворачивание
18
+ контента в адаптиве: на узком экране виден только заголовок, остальное раскрывается по клику
19
+ по шапке.
21
20
 
22
- Используй когда: нужен адаптивный виджет на дашборде/пульте, который
23
- схлопывается на мобиле. Если адаптивность не нужна — `Island` проще.
21
+ Используй когда: блок дашборда со сгруппированной информацией, набором связанных действий
22
+ или отдельной функциональностью, который на мобильном должен схлопываться в одну строку.
23
+
24
+ Не используй когда:
25
+ - Нужна просто карточка без сворачивания и без готовой схемы отступов — возьми `Island`.
26
+ - Нужен список раскрывающихся секций, где раскрытием управляет потребитель и оно работает на
27
+ всех ширинах — возьми `IslandAccordion`. У `IslandWidget` сворачивание есть только в адаптиве
28
+ и состоянием изнутри не управляется.
29
+ - Нужен overlay или модальное окно — возьми `ModalWindow`, `LightBox`.
24
30
 
25
31
  ---
26
32
 
27
- ## Файловая структура
33
+ ## Варианты и props
28
34
 
29
- ```text
30
- src/components/IslandWidget/
31
- ├── IslandWidget.tsx # Object.assign с Header/Body/Footer/ExtraFooter
32
- ├── IslandWidgetContext.tsx # { adaptive, disableAdaptiveCollapsing, open }
33
- ├── IslandWidgetLayoutContext.tsx # { hasExtraFooter, setHasExtraFooter }
34
- ├── index.ts # Barrel
35
- ├── components/
36
- │ ├── IslandWidgetHeader.tsx # FC + статические .Content/.Description, chevron при adaptive
37
- │ ├── IslandWidgetHeaderContent.tsx
38
- │ ├── IslandWidgetHeaderDescription.tsx
39
- │ ├── IslandWidgetBody.tsx # FC
40
- │ ├── IslandWidgetFooter.tsx # FC + статические .Content/.Controls
41
- │ ├── IslandWidgetFooterContent.tsx
42
- │ ├── IslandWidgetFooterControls.tsx
43
- │ ├── IslandWidgetExtraFooter.tsx # FC, управляемое open (ExpandAnimation)
44
- │ └── IslandWidgetWrapper.tsx # forwardRef, Provider IslandWidgetLayoutContext
45
- └── styles/*.module.less
46
- ```
35
+ ### Обязательные props
47
36
 
48
- ---
37
+ | Prop | Тип | Описание |
38
+ |---|---|---|
39
+ | `renderHeader` | `(props: IIslandWidgetHeaderProps) => React.ReactNode` | Рендер-функция шапки |
40
+ | `renderBody` | `(props: IIslandWidgetBodyProps) => React.ReactNode` | Рендер-функция тела |
41
+
42
+ ### Опциональные props
43
+
44
+ | Prop | Тип | По умолчанию | Описание |
45
+ |---|---|---|---|
46
+ | `size` | `EComponentSize` | `EComponentSize.MD` | Скругление карточки и отступы всех частей виджета |
47
+ | `renderFooter` | `(props: IIslandWidgetFooterProps) => React.ReactNode` | `undefined` | Рендер-функция подвала. Без неё подвал не рендерится |
48
+ | `disableAdaptiveCollapsing` | `boolean` | `false` | Отключает сворачивание контента в адаптиве — контент всегда виден, индикатор раскрытия не рисуется |
49
+
50
+ Компонент расширяет `React.HTMLAttributes<HTMLDivElement>` — стандартные атрибуты `<div>`
51
+ (`className`, `style`, `role`, `aria-*`, `data-*`, обработчики) попадают на корневой элемент.
52
+
53
+ ### Render-функции, а не children
54
+
55
+ Части виджета передаются тремя render-функциями. Каждая получает объект props (сейчас пустой)
56
+ и обязана раскрыть его в соответствующую часть — так у частей остаётся канал для props,
57
+ которые виджет может начать прокидывать позже:
49
58
 
50
- ## Compound-структура
59
+ ```tsx
60
+ <IslandWidget
61
+ renderHeader={(props) => (
62
+ <IslandWidget.Header {...props}>
63
+ <IslandWidget.Header.Title>Заголовок</IslandWidget.Header.Title>
64
+ </IslandWidget.Header>
65
+ )}
66
+ renderBody={(props) => <IslandWidget.Body {...props}>Контент</IslandWidget.Body>}
67
+ />
68
+ ```
69
+
70
+ `children` виджет **молча отбрасывает**: они попадают в `...rest`, но корневой `<div>` уже имеет
71
+ собственных JSX-детей (`Island`), и они перекрывают `children` из спреда. Ни ошибки, ни
72
+ предупреждения не будет — контент просто не отрисуется. Всё содержимое кладётся через
73
+ render-функции.
74
+
75
+ ### Составные части
76
+
77
+ | Часть | Barrel-экспорт | Назначение |
78
+ |---|---|---|
79
+ | `IslandWidget.Header` | `IslandWidgetHeader` | Шапка. В адаптиве получает индикатор раскрытия |
80
+ | `IslandWidget.Header.Title` | — | Заголовок (`Title` H3 medium). На десктопе однострочный с многоточием, в адаптиве переносится |
81
+ | `IslandWidget.Header.Description` | `IslandWidgetHeaderDescription` | Описание (`Text`, вторичный цвет). На десктопе прижато вправо, в адаптиве уходит на свою строку |
82
+ | `IslandWidget.Header.Controls` | — | Контролы шапки. Гасит всплытие клика |
83
+ | `IslandWidget.Body` | `IslandWidgetBody` | Основное содержимое |
84
+ | `IslandWidget.Footer` | `IslandWidgetFooter` | Подвал |
85
+ | `IslandWidget.Footer.Content` | — | Левая часть подвала, занимает свободное место |
86
+ | `IslandWidget.Footer.Controls` | — | Правая часть подвала. В адаптиве переносится на новую строку и выравнивается влево |
87
+ | `IslandWidget.ExtraFooter` | — | Дополнительный подвал **под** карточкой |
88
+ | — | `IslandWidgetWrapper` | Обёртка виджета и `ExtraFooter` |
89
+ | — | `IslandWidgetHeaderContent` | Обёртка содержимого шапки. К `IslandWidget.Header` не привязана |
90
+
91
+ Отступы частей берутся из `size` виджета через внутренний контекст — отрендеренная отдельно
92
+ `IslandWidgetBody` получит только `className` без размерного класса.
93
+
94
+ ### Сворачивание в адаптиве
95
+
96
+ - Адаптив определяется хуком `useMobileView()` — медиа-запрос `max-width: 767px`
97
+ (`EScreenWidth.SM_MAX`), тот же порог, что у компонента `MobileView`.
98
+ - В адаптиве обработчик клика вешается на обёртку вокруг шапки, а тело и подвал заворачиваются
99
+ в `ExpandAnimation`. На десктопе обработчика нет и `ExpandAnimation` не используется —
100
+ контент рендерится напрямую.
101
+ - Начальное состояние раскрытия равно `disableAdaptiveCollapsing`: по умолчанию виджет
102
+ смонтирован свёрнутым, а с `disableAdaptiveCollapsing={true}` — раскрытым. Значение уходит
103
+ во внутренний контекст и доходит до разметки: `IslandWidgetHeader` вешает класс `open`
104
+ безусловно, без оглядки на `adaptive`, поэтому при `disableAdaptiveCollapsing={true}` шапка
105
+ получает этот класс сразу и на десктопе тоже. Видимого эффекта нет — единственное правило на
106
+ `.open` живёт внутри `@media (max-width: @screen-sm-max)` и вращает каретку, которой в этом
107
+ режиме не существует. При рефакторинге шапки на это рассчитывать нельзя.
108
+ - Обработчик клика по шапке навешивается по `expandableContent`, то есть только когда
109
+ сворачивание реально доступно. При `disableAdaptiveCollapsing={true}` обработчика на шапке
110
+ нет вовсе, поэтому тап по ней не дёргает `setOpen` и не переключает класс `open`.
111
+ - `disableAdaptiveCollapsing` читается только как начальное значение `useState`, поэтому смена
112
+ этого prop'а после монтирования не пересчитывает уже накопленное состояние раскрытия —
113
+ она лишь выключает или включает саму обёртку `ExpandAnimation`.
114
+ - Состояние раскрытия внутреннее: props, чтобы прочитать или задать его снаружи, нет.
115
+ - `IslandWidget.Header.Controls` вызывает `stopPropagation` на клике, поэтому взаимодействие с
116
+ кнопками и полями в шапке не сворачивает виджет. Любой другой интерактивный элемент,
117
+ положенный в шапку мимо `Controls`, будет сворачивать виджет по клику.
118
+
119
+ ### ExtraFooter
120
+
121
+ `ExtraFooter` — не часть карточки: он рендерится **соседом** виджета, а не внутри него.
122
+ Оба должны лежать в общем `IslandWidgetWrapper` — через его контекст `ExtraFooter` сообщает
123
+ виджету, что раскрыт, и виджет рисует под собой тень (`::after` с
124
+ `IslandWidget.ExtraFooter_Shadow`).
51
125
 
52
126
  ```tsx
53
127
  <IslandWidgetWrapper>
54
- <IslandWidget
55
- renderHeader={(props) => (
56
- <IslandWidget.Header {...props}>
57
- <IslandWidget.Header.Content>Заголовок</IslandWidget.Header.Content>
58
- <IslandWidget.Header.Description>Описание</IslandWidget.Header.Description>
59
- </IslandWidget.Header>
60
- )}
61
- renderBody={(props) => <IslandWidget.Body {...props}>{...}</IslandWidget.Body>}
62
- renderFooter={(props) => (
63
- <IslandWidget.Footer {...props}>
64
- <IslandWidget.Footer.Content>...</IslandWidget.Footer.Content>
65
- <IslandWidget.Footer.Controls>
66
- <Button>...</Button>
67
- </IslandWidget.Footer.Controls>
68
- </IslandWidget.Footer>
69
- )}
70
- />
71
- <IslandWidget.ExtraFooter open={extraOpen}>
72
- Дополнительный блок снизу
73
- </IslandWidget.ExtraFooter>
128
+ <IslandWidget renderHeader={renderHeader} renderBody={renderBody} />
129
+ <IslandWidget.ExtraFooter open={open}>Дополнительный контент</IslandWidget.ExtraFooter>
74
130
  </IslandWidgetWrapper>
75
131
  ```
76
132
 
77
- `IslandWidgetWrapper` опционален — нужен только если используешь `ExtraFooter`
78
- (иначе `hasExtraFooter` всегда `false` и стили корня не подстраиваются).
133
+ - `open` — контролируемый prop (по умолчанию `false`), раскрытием управляет потребитель.
134
+ Раскрытие идёт через `ExpandAnimation`, как и сворачивание виджета в адаптиве.
135
+ - Без `IslandWidgetWrapper` `ExtraFooter` работает, но виджет о нём не узнает: контекст по
136
+ умолчанию содержит no-op колбэки, тень не появится.
137
+ - `ExtraFooter` подтягивается под карточку отрицательным `margin-top: -24px` и компенсирующим
138
+ `padding-top`, поэтому визуально выглядит продолжением карточки.
139
+ - Обёртка считает раскрытые подвалы, а не хранит флаг: каждый раскрытый `ExtraFooter`
140
+ регистрируется в контексте на всё время, пока `open === true`, и снимается с учёта
141
+ и при закрытии, и при размонтировании. Поэтому условный рендеринг подвала так же
142
+ корректен, как управление через `open` (тень не залипает), а если в одной обёртке
143
+ раскрыто несколько подвалов, закрытие или удаление одного не снимает тень, пока
144
+ открыт хотя бы один. `hasExtraFooter` в контексте — это «счётчик > 0».
145
+ - Если виджету нужна фиксированная высота, задавай её на `IslandWidgetWrapper`, а не на
146
+ `IslandWidget` (story `Example: ExtraFooter with wrapper height`).
147
+
148
+ ### Размеры
149
+
150
+ `size` задаёт отступы частей; скругление карточки приходит из `Island` того же размера.
151
+
152
+ | `size` | Header | Body (горизонтально) | Footer | Адаптив (`max-width: 767px`) |
153
+ |---|---|---|---|---|
154
+ | `SM` | 16px 16px 12px | 16px | 16px | Header 16px, место под индикатор справа |
155
+ | `MD` | 24px 24px 16px | 24px | 24px | Схлопывается до 16px |
156
+ | `LG` | 32px 32px 24px | 32px | 32px | Схлопывается до 24px |
157
+
158
+ `Island` рендерится с `withoutPaddings={true}` — все внутренние отступы принадлежат частям
159
+ виджета, а не карточке.
160
+
161
+ Размер описания в шапке зависит и от `size`, и от ширины экрана: на десктопе всегда `B4`,
162
+ в адаптиве — `B4` для `SM` и `B3` для `MD` и `LG`.
79
163
 
80
164
  ---
81
165
 
82
- ## Варианты и props
166
+ ## Дизайн-токены
83
167
 
84
- ### IslandWidget — `IIslandWidgetProps extends HTMLAttributes<HTMLDivElement>`
168
+ Переопределяются через `ThemeProvider` (prop `tokens`) — см. `ThemeProvider-ai.md` →
169
+ «Как переопределять токены». Значения по умолчанию —
170
+ `src/components/DesignTokens/components/IslandWidget.ts`.
85
171
 
86
- | Prop | Тип | Обязателен | Описание |
87
- |---|---|---|---|
88
- | `renderHeader` | `(props: IIslandWidgetHeaderProps) => ReactNode` | Да | Рендер-функция заголовка. Получает пустой объект — можно использовать `IslandWidget.Header` с любыми child'ами. |
89
- | `renderBody` | `(props: IIslandWidgetBodyProps) => ReactNode` | Да | Рендер-функция Body. |
90
- | `renderFooter` | `(props: IIslandWidgetFooterProps) => ReactNode` | Нет | Необязательный Footer. |
91
- | `disableAdaptiveCollapsing` | `boolean` | Нет (default `false`) | Отключает автоматическое сворачивание на мобиле. При `true` — заголовок некликабельный, chevron не рендерится. |
172
+ ```text
173
+ IslandWidget.ExtraFooter_Background
174
+ IslandWidget.ExtraFooter_Shadow
175
+ ```
92
176
 
93
- **Почему через render-props, а не children:** IslandWidget оборачивает контент в `ExpandAnimation` на adaptive, и ему нужно знать структуру слотов. Render-props позволяют вставлять слоты именно в нужные места (внутрь/снаружи ExpandAnimation).
177
+ Оба токена относятся к дополнительному подвалу: фон самого `ExtraFooter` и тень, которую
178
+ виджет рисует над ним, когда тот раскрыт. Фон и тень самой карточки — токены `Island`
179
+ (`Island.Type1_Background`, `Island.Type1_Shadow`), виджет их не переопределяет. Скругления
180
+ и отступы токенами не описываются — они заданы литералами в `styles/*.module.less`.
94
181
 
95
- ### IslandWidget.Header — `IIslandWidgetHeaderProps extends HTMLAttributes<HTMLDivElement>`
182
+ ---
96
183
 
97
- FC (не forwardRef). Не управляемый; читает `IslandWidgetContext` и рендерит chevron если `adaptive && !disableAdaptiveCollapsing`. Имеет статические `.Content` и `.Description`.
184
+ ## Инварианты
98
185
 
99
- ### IslandWidget.Footer — `IIslandWidgetFooterProps extends HTMLAttributes<HTMLDivElement>`
186
+ - **`forwardRef`** — обязателен у `IslandWidget` и `IslandWidgetWrapper`, `ref` идёт на корневой
187
+ `<div>`. Части виджета (`Header`, `Body`, `Footer`, `ExtraFooter` и их подчасти) — `React.FC`
188
+ без `ref`; добавление `forwardRef` частям расширяет API и требует отдельного решения.
189
+ - **Публичный API:** из `src/components/IslandWidget/index.ts` экспортируются `IslandWidget`,
190
+ `IslandWidgetHeader`, `IslandWidgetHeaderContent`, `IslandWidgetHeaderDescription`,
191
+ `IslandWidgetBody`, `IslandWidgetFooter`, `IslandWidgetWrapper` и их интерфейсы props
192
+ (`IIslandWidgetProps`, `IIslandWidgetHeaderProps`, `IIslandWidgetBodyProps`,
193
+ `IIslandWidgetFooterProps`, `IIslandWidgetWrapperProps`), а также типы
194
+ `TIslandWidgetHeader` и `TIslandWidgetFooter`.
195
+ - **`IslandWidgetExtraFooter`, `IslandWidgetHeaderTitle`, `IslandWidgetHeaderControls`,
196
+ `IslandWidgetFooterContent`, `IslandWidgetFooterControls` в barrel не экспортируются** — они
197
+ доступны только как `IslandWidget.ExtraFooter`, `IslandWidget.Header.Title` и т.д. Это
198
+ сознательная асимметрия: добавление их в barrel расширит публичный API.
199
+ - **`IslandWidgetContext` и `IslandWidgetLayoutContext` — внутренние**, в barrel не выходят.
200
+ Их интерфейсы можно менять без breaking change.
201
+ - **Корневой элемент — `<div>`** у виджета, обёртки и всех частей. Смена тега ломает вёрстку
202
+ потребителей: селекторы отступов класс-базированные, но `display` по умолчанию завязан на тег.
203
+ - **`data-tx` потребитель переопределить не может** — атрибут ставится после `...rest`, как у
204
+ `Header` и `UnorderedListExtended`.
205
+ - **`renderHeader` / `renderBody` / `renderFooter` вызываются с пустым объектом.** Не удаляй
206
+ параметр из сигнатуры и не заменяй render-функции на `children` — это breaking change.
207
+ - **`IslandWidgetHeaderContent` не имеет своих стилей:** на элемент попадает только `className`
208
+ потребителя, раскладку задаёт родительская шапка. Компонент экспортируется из barrel и
209
+ к `IslandWidget.Header` не привязан. Если понадобится собственное оформление — объявляй
210
+ класс в `styles/IslandWidgetHeader.module.less` и подключай его явно, а не рассчитывай
211
+ на ранее ссылавшийся здесь несуществующий `styles.islandWidgetHeaderContent`.
212
+ - **`box-shadow` тени `ExtraFooter` больше не помечен `!important`.** Помета снята: в библиотеке
213
+ нет ни одного другого правила на `.islandWidgetWithExtraFooter::after`, перебивать было нечего.
214
+ Возвращать помету не нужно — если тень вдруг начнёт перебиваться, ищи появившееся
215
+ конкурирующее правило, а не возвращай `!important`.
100
216
 
101
- FC. Имеет статические `.Content` и `.Controls` — типовой паттерн "описание + кнопки".
217
+ ---
102
218
 
103
- ### IslandWidget.Body — `IIslandWidgetBodyProps`
219
+ ## Accessibility
104
220
 
105
- FC, без подслотов.
221
+ Сворачивание в адаптиве **не озвучено и не доступно с клавиатуры**: обработчик клика висит на
222
+ обычном `<div>`-обёртке вокруг шапки, у которой нет ни `role="button"`, ни `tabIndex`, ни
223
+ `aria-expanded` / `aria-controls`. Индикатор раскрытия — декоративная иконка с
224
+ `aria-hidden="true"`. Пользователь клавиатуры и скринридера на мобильной ширине не может
225
+ раскрыть контент.
106
226
 
107
- ### IslandWidget.ExtraFooter — `IIslandWidgetExtraFooterProps`
227
+ Обходные пути до исправления:
228
+ - Передать `disableAdaptiveCollapsing`, если содержимое обязано быть доступным на всех ширинах.
229
+ - Положить собственную кнопку раскрытия в `IslandWidget.Header.Controls` нельзя — состояние
230
+ раскрытия внутреннее и снаружи не управляется.
108
231
 
109
- | Prop | Тип | Default | Описание |
110
- |---|---|---|---|
111
- | `open` | `boolean` | `false` | Управляемое состояние раскрытия. |
232
+ Остальное — на потребителе:
233
+ - `IslandWidget.Header.Title` рендерит `Title` тегом по умолчанию; если карточке нужен
234
+ семантический заголовок уровня страницы, задай тег через props `Title` или положи свой `<h*>`.
235
+ - Кнопки-иконки в `Header.Controls` требуют собственного `aria-label` — библиотека
236
+ мультиязычная и текст не хардкодит.
237
+ - Роль и `aria-*` для карточки целиком передаются в `IslandWidget` через `...rest`.
112
238
 
113
- - Оборачивает содержимое в `ExpandAnimation expanded={open}`.
114
- - В `useEffect` пишет `setHasExtraFooter(open)` в `IslandWidgetLayoutContext` — корневой `IslandWidget` использует этот флаг для корректировки стилей корня (добавляет `.islandWidgetWithExtraFooter`).
239
+ ---
115
240
 
116
- ### IslandWidgetWrapper
241
+ ## Связанные компоненты
117
242
 
118
- ForwardRef. Provider `IslandWidgetLayoutContext`. Нужен только для корректной работы `ExtraFooter`.
243
+ ### Альтернативы (в `related`)
119
244
 
120
- ---
245
+ - `IslandAccordion` — раскрывающиеся секции на `Island`. Путать легко: оба сворачивают контент
246
+ внутри карточки. Разница — у аккордеона раскрытие работает на всех ширинах и им управляет
247
+ потребитель, у виджета оно только в адаптиве и состояние внутреннее.
121
248
 
122
- ## Ключевые особенности реализации
249
+ ### Контракты по рендеру (в `related`)
123
250
 
124
- ### Adaptive через `useMatchMedia`
251
+ - `Island` — контейнер, который виджет рендерит внутри себя с `type={EIslandType.TYPE_1}`,
252
+ `withoutPaddings={true}` и своим `size`. Отсюда берутся фон, тень и скругление карточки.
253
+ Части `Island` (`Island.Header` / `Body` / `Footer`) виджет **не** использует — у него свои.
254
+ - `ExpandAnimation` — анимация сворачивания. Используется дважды: для контента виджета в
255
+ адаптиве и внутри `IslandWidget.ExtraFooter`. В свёрнутом состоянии выставляет
256
+ `visibility: hidden`, поэтому контент не получает фокус по Tab.
257
+ - `Text` — `IslandWidget.Header.Description` рендерится через него (`EFontType.SECONDARY`,
258
+ размер по `size` и ширине экрана).
259
+ - `Title` — `IslandWidget.Header.Title` рендерится через него (`ETitleSize.H3`,
260
+ `EFontWeightTitle.MEDIUM`).
125
261
 
126
- ```ts
127
- const adaptive = useMatchMedia(
128
- `(max-width: ${EScreenWidth.SM_MAX})`,
129
- window.innerWidth <= parseInt(EScreenWidth.SM_MAX),
130
- );
131
- ```
262
+ ### Зависимости без отдельной связи (в `related` не входят)
132
263
 
133
- - Первым аргументом — media query, вторым — initial fallback для SSR-safe значения.
134
- - `window.innerWidth` вычисляется на этапе рендера — **не SSR-safe** (упадёт на сервере). Потребители в SSR должны оборачивать виджет в `ClientOnly`-паттерн.
264
+ - `MobileView` — виджет использует его хук `useMobileView()`, но сам компонент не рендерит.
265
+ Порог адаптива у них общий, менять его нужно синхронно.
135
266
 
136
- ### Управление открытием — внутреннее `useState`
267
+ ### Части виджета (своего AI.md не имеют)
137
268
 
138
- ```ts
139
- const [open, setOpen] = useState(disableAdaptiveCollapsing);
140
- ```
269
+ Все части описаны выше в разделе «Составные части»: `IslandWidgetHeader` с
270
+ `Title` / `Description` / `Controls`, `IslandWidgetBody`, `IslandWidgetFooter` с
271
+ `Content` / `Controls`, `IslandWidgetExtraFooter`, `IslandWidgetWrapper` и
272
+ `IslandWidgetHeaderContent`. Собственных props сверх `className + ...rest` у них нет,
273
+ кроме `open` у `ExtraFooter`.
141
274
 
142
- - Если adaptive не включён — поведение не работает.
143
- - `open` инициализируется значением `disableAdaptiveCollapsing`: без адаптивного сворачивания виджет открыт всегда (это защита, если `disableAdaptiveCollapsing` меняется рантайм).
144
- - Нет prop'а `defaultOpen` / `opened` / `onToggle` — открытие полностью неуправляемое.
145
- - Клик по заголовку → `handleHeaderClick` → `setOpen(!open)`. Срабатывает **только если adaptive**.
275
+ ---
146
276
 
147
- ### ExpandAnimation оборачивает Body + Footer (но не Header)
277
+ ## Stories
148
278
 
149
- ```tsx
150
- {expandableContent
151
- ? <ExpandAnimation expanded={open}>{renderContent()}</ExpandAnimation>
152
- : <>{renderContent()}</>}
153
- ```
279
+ Основные истории: `stories/IslandWidget/IslandWidget.stories.tsx`
280
+ Файлы примеров: `stories/IslandWidget/examples/`
154
281
 
155
- На desktop (не adaptive) или при `disableAdaptiveCollapsing=true` — без анимации, контент виден сразу.
282
+ | Story | Example file | Что демонстрирует |
283
+ |---|---|---|
284
+ | `Playground` | — | Интерактивный контроль `disableAdaptiveCollapsing` на виджете с шапкой, телом и подвалом |
285
+ | `Default` | `DefaultExample.tsx` | Полный состав: Header с Title / Controls / Description, Body, Footer |
286
+ | `Sizes` | `SizesExample.tsx` | Размеры SM / MD / LG: отступы частей и скругление карточки |
287
+ | `WithoutFooter` | `WithoutFooterExample.tsx` | Виджет без `renderFooter` — нижний отступ берёт на себя Body |
288
+ | `WithFooterAndExtraFooter` | `WithFooterAndExtraFooterExample.tsx` | `ExtraFooter` рядом с подвалом внутри `IslandWidgetWrapper`, тень под карточкой |
289
+ | `WithoutFooterAndWithExtraFooter` | `WithoutFooterAndWithExtraFooterExample.tsx` | `ExtraFooter` без подвала |
290
+ | `WithExtraFooterAndIslandWidgetHeight` | `WithExtraFooterAndIslandWidgetHeightExample.tsx` | Фиксированная высота задаётся на `IslandWidgetWrapper` |
156
291
 
157
- ### `onClick` на wrapper'е Header'а, а не на Header'е
292
+ Файлы примеров с постфиксом `Example` — локальный legacy-паттерн папки.
158
293
 
159
- ```tsx
160
- <div
161
- className={clsx({ [styles.islandWidgetHeaderWrapperAdaptive]: adaptive })}
162
- onClick={adaptive ? handleHeaderClick : undefined}
163
- >
164
- {renderHeader({})}
165
- </div>
166
- ```
294
+ ---
167
295
 
168
- - Click-target — обёртка, а не сам header. Это упрощает CSS (hover только на adaptive).
169
- - **Нет `role="button"`/`tabIndex`/keyboard-навигации** на wrapper'е — клик только мышью. Это a11y-gap.
296
+ ## История изменений
170
297
 
171
- ### `hasExtraFooter` в LayoutContext
298
+ | Дата | Изменение |
299
+ |---|---|
300
+ | 2026-09-09 | Создан документ. AI-рефакторинг: JSDoc на props, частях виджета и внутренних контекстах, unit-тесты на все части, сворачивание в адаптиве, `forwardRef`, `className` и размеры. Исправлено: `className` в `IslandWidget.Footer.Content` и `IslandWidget.Footer.Controls` больше не затирает базовый класс; `data-tx` перенесён после `...rest` и больше не переопределяется потребителем. Публичный API не изменён. |
172
301
 
173
- ```tsx
174
- <div className={clsx(styles.islandWidget, className, {
175
- [styles.islandWidgetWithExtraFooter]: hasExtraFooter,
176
- })}>
177
- ```
302
+ ---
178
303
 
179
- Корневой Island меняет стиль (например, нижний радиус), когда снизу есть ExtraFooter. Это делает Wrapper'ом эффективным лишь для пары "IslandWidget + ExtraFooter". Без Wrapper'а контекст дефолтный (`hasExtraFooter=false`).
304
+ ## Примеры использования
180
305
 
181
- ---
306
+ ### Default
182
307
 
183
- ## Инварианты
308
+ `stories/IslandWidget/examples/DefaultExample.tsx`
184
309
 
185
- - **Render-props API (`renderHeader`/`renderBody`/`renderFooter`)** — публичный контракт. Не заменять на children без миграции потребителей.
186
- - **Дочерние `Header`/`Body`/`Footer`** — `React.FC` (без forwardRef). Обёртки легковесные, но не прокидывают ref наружу.
187
- - **`IslandWidgetHeader` / `IslandWidgetFooter`** — статические подкомпоненты через **не Object.assign**, а через `TIslandWidgetHeader = FC & { Content, Description }` + присваивание полей после определения. При изменении сохранить паттерн.
188
- - **`IslandWidget.Body`** — не экспортируется в barrel прямо (`export * from "./IslandWidgetBody"` в components/index.ts есть, но проверить отдельно потребителей). Проверь экспорт перед изменением.
189
- - **`useMatchMedia` + `window.innerWidth` на инициализации** — **не SSR-safe**. Не переносить в SSR-контекст без защиты.
190
- - **Клик-target — wrapper Header'а**. Нет ARIA/keyboard для коллапса — известный a11y-debt.
191
- - **`Island` хардкожен как `TYPE_1 / MD`** внутри — если нужен другой тип/размер, нужен рефакторинг с пропагацией через props.
192
- - **`ExtraFooter` требует `IslandWidgetWrapper`** для корректного стиля нижнего радиуса корневого Island.
193
- - **`ExpandAnimation`** оборачивает только Body+Footer; Header всегда виден.
310
+ ```tsx
311
+ import React, { useState } from "react";
312
+ import {
313
+ IslandWidget,
314
+ Button,
315
+ ButtonIcon,
316
+ EComponentSize,
317
+ EButtonTheme,
318
+ Link,
319
+ Text,
320
+ ETextSize,
321
+ EFontType,
322
+ DateField,
323
+ EFormFieldStatus,
324
+ } from "@sberbusiness/triplex-next";
325
+ import { SettingsStrokeSrvIcon20 } from "@sberbusiness/icons-next";
326
+
327
+ export const DefaultExample = () => {
328
+ const [value, setValue] = useState("");
329
+
330
+ return (
331
+ <IslandWidget
332
+ renderBody={(props) => <IslandWidget.Body {...props}>Content</IslandWidget.Body>}
333
+ renderFooter={(props) => (
334
+ <IslandWidget.Footer {...props}>
335
+ <IslandWidget.Footer.Content>
336
+ <Text tag="div" size={ETextSize.B3} type={EFontType.SECONDARY}>
337
+ <Link>Link text</Link>
338
+ </Text>
339
+ </IslandWidget.Footer.Content>
340
+ <IslandWidget.Footer.Controls>
341
+ <Button theme={EButtonTheme.SECONDARY} size={EComponentSize.SM}>
342
+ Button text
343
+ </Button>
344
+ <Button theme={EButtonTheme.GENERAL} size={EComponentSize.SM}>
345
+ Button text
346
+ </Button>
347
+ </IslandWidget.Footer.Controls>
348
+ </IslandWidget.Footer>
349
+ )}
350
+ renderHeader={(props) => (
351
+ <IslandWidget.Header {...props}>
352
+ <IslandWidget.Header.Title>Title</IslandWidget.Header.Title>
353
+ <IslandWidget.Header.Controls>
354
+ <ButtonIcon>
355
+ <SettingsStrokeSrvIcon20 paletteIndex={5} />
356
+ </ButtonIcon>
357
+ <DateField
358
+ value={value}
359
+ onChange={setValue}
360
+ className="island-widget-date-field"
361
+ placeholderMask="дд.мм.гггг"
362
+ label="дд.мм.гггг"
363
+ invalidDateHint="Указана недоступная для выбора дата."
364
+ size={EComponentSize.SM}
365
+ status={EFormFieldStatus.DEFAULT}
366
+ />
367
+ </IslandWidget.Header.Controls>
368
+ <IslandWidget.Header.Description>Description</IslandWidget.Header.Description>
369
+ </IslandWidget.Header>
370
+ )}
371
+ />
372
+ );
373
+ };
374
+ ```
194
375
 
195
- ---
376
+ ### Sizes
196
377
 
197
- ## Accessibility
378
+ `stories/IslandWidget/examples/SizesExample.tsx`
198
379
 
199
- - Клик по header'у на мобиле **не доступен с клавиатуры** (нет `tabIndex`/`role="button"`/keyboard handler). Known debt. Для критичных сценариев используйте `disableAdaptiveCollapsing` и навигацию через iOS/Android native.
200
- - Chevron-иконка — `aria-hidden="true"`, правильно.
201
- - Нет `aria-expanded`/`aria-controls` на wrapper'е — скринридер не анонсирует состояние. Known debt.
202
- - Остальное — прокидывается через `...htmlDivAttributes`.
380
+ ```tsx
381
+ import React, { useState } from "react";
382
+ import {
383
+ IslandWidget,
384
+ Button,
385
+ ButtonIcon,
386
+ EComponentSize,
387
+ EButtonTheme,
388
+ Link,
389
+ Text,
390
+ ETextSize,
391
+ EFontType,
392
+ DateField,
393
+ EFormFieldStatus,
394
+ } from "@sberbusiness/triplex-next";
395
+ import { SettingsStrokeSrvIcon20 } from "@sberbusiness/icons-next";
396
+
397
+ interface ISizeItemProps {
398
+ size: EComponentSize;
399
+ }
400
+
401
+ const SizeItem = ({ size }: ISizeItemProps) => {
402
+ const [value, setValue] = useState("");
403
+
404
+ const controlSize = size === EComponentSize.SM ? EComponentSize.SM : EComponentSize.MD;
405
+
406
+ return (
407
+ <IslandWidget
408
+ size={size}
409
+ renderBody={(props) => <IslandWidget.Body {...props}>Content</IslandWidget.Body>}
410
+ renderFooter={(props) => (
411
+ <IslandWidget.Footer {...props}>
412
+ <IslandWidget.Footer.Content>
413
+ <Text
414
+ tag="div"
415
+ size={size === EComponentSize.LG ? ETextSize.B2 : ETextSize.B3}
416
+ type={EFontType.SECONDARY}
417
+ >
418
+ <Link>Link text</Link>
419
+ </Text>
420
+ </IslandWidget.Footer.Content>
421
+ <IslandWidget.Footer.Controls>
422
+ <Button theme={EButtonTheme.SECONDARY} size={controlSize}>
423
+ Button text
424
+ </Button>
425
+ <Button theme={EButtonTheme.GENERAL} size={controlSize}>
426
+ Button text
427
+ </Button>
428
+ </IslandWidget.Footer.Controls>
429
+ </IslandWidget.Footer>
430
+ )}
431
+ renderHeader={(props) => (
432
+ <IslandWidget.Header {...props}>
433
+ <IslandWidget.Header.Title>Title</IslandWidget.Header.Title>
434
+ <IslandWidget.Header.Controls>
435
+ <ButtonIcon>
436
+ <SettingsStrokeSrvIcon20 paletteIndex={5} />
437
+ </ButtonIcon>
438
+ <DateField
439
+ value={value}
440
+ onChange={setValue}
441
+ className="island-widget-date-field"
442
+ placeholderMask="дд.мм.гггг"
443
+ label="дд.мм.гггг"
444
+ invalidDateHint="Указана недоступная для выбора дата."
445
+ size={controlSize}
446
+ status={EFormFieldStatus.DEFAULT}
447
+ />
448
+ </IslandWidget.Header.Controls>
449
+ <IslandWidget.Header.Description>Description</IslandWidget.Header.Description>
450
+ </IslandWidget.Header>
451
+ )}
452
+ />
453
+ );
454
+ };
455
+
456
+ const SIZES = Object.values(EComponentSize);
457
+
458
+ export const SizesExample = () => (
459
+ <div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
460
+ {SIZES.map((size) => (
461
+ <div key={size}>
462
+ <div style={{ marginBottom: "8px", fontSize: "16px", fontWeight: "700" }}>{size.toUpperCase()}</div>
463
+ <SizeItem size={size} />
464
+ </div>
465
+ ))}
466
+ </div>
467
+ );
468
+ ```
203
469
 
204
- ---
470
+ ### WithoutFooter
205
471
 
206
- ## Дизайн-токены
472
+ `stories/IslandWidget/examples/WithoutFooterExample.tsx`
207
473
 
208
- Нет собственных (все от `Island.TYPE_1`: `--triplex-next-Island-Type1_Background` / `_Shadow`).
474
+ ```tsx
475
+ import React from "react";
476
+ import { IslandWidget } from "@sberbusiness/triplex-next";
209
477
 
210
- ---
478
+ export const WithoutFooterExample = () => (
479
+ <IslandWidget
480
+ renderBody={(props) => <IslandWidget.Body {...props}>Content</IslandWidget.Body>}
481
+ renderHeader={(props) => (
482
+ <IslandWidget.Header {...props}>
483
+ <IslandWidget.Header.Title>Title</IslandWidget.Header.Title>
484
+ <IslandWidget.Header.Description>Description</IslandWidget.Header.Description>
485
+ </IslandWidget.Header>
486
+ )}
487
+ />
488
+ );
489
+ ```
211
490
 
212
- ## Связанные компоненты
491
+ ### WithFooterAndExtraFooter
213
492
 
214
- - `Island` — корневая карточка (hardcoded TYPE_1/MD внутри).
215
- - `IslandAccordion` — альтернатива для списка раскрывающихся элементов.
216
- - `ExpandAnimation` — используется для анимации сворачивания.
217
- - `MediaWidth.useMatchMedia` — детектирует адаптив.
218
- - `EScreenWidth` (`@sberbusiness/triplex-next/helpers/breakpoints`) — breakpoint SM_MAX.
493
+ `stories/IslandWidget/examples/WithFooterAndExtraFooterExample.tsx`
219
494
 
220
- ---
495
+ ```tsx
496
+ import React, { useState } from "react";
497
+ import {
498
+ IslandWidget,
499
+ IslandWidgetWrapper,
500
+ Button,
501
+ EButtonTheme,
502
+ EComponentSize,
503
+ Link,
504
+ Text,
505
+ ETextSize,
506
+ EFontType,
507
+ } from "@sberbusiness/triplex-next";
508
+
509
+ export const WithFooterAndExtraFooterExample = () => {
510
+ const [extraFooterOpen, setExtraFooterOpen] = useState(false);
511
+ const [bodyHeight, setBodyHeight] = useState(260);
512
+
513
+ const handleExtraFooterOpen = (open: boolean) => {
514
+ setExtraFooterOpen(open);
515
+ setBodyHeight(open ? 196 : 260);
516
+ };
517
+
518
+ return (
519
+ <>
520
+ <div className="island-widget-control-panel">
521
+ <Button
522
+ theme={EButtonTheme.GENERAL}
523
+ size={EComponentSize.SM}
524
+ onClick={() => handleExtraFooterOpen(true)}
525
+ >
526
+ Open Extra Footer
527
+ </Button>
528
+ <Button
529
+ theme={EButtonTheme.GENERAL}
530
+ size={EComponentSize.SM}
531
+ onClick={() => handleExtraFooterOpen(false)}
532
+ >
533
+ Close Extra Footer
534
+ </Button>
535
+ </div>
536
+
537
+ <IslandWidgetWrapper>
538
+ <IslandWidget
539
+ renderBody={(props) => (
540
+ <IslandWidget.Body {...props}>
541
+ <div style={{ height: bodyHeight, transition: "height 0.3s ease-in-out" }}>Content</div>
542
+ </IslandWidget.Body>
543
+ )}
544
+ renderFooter={(props) => (
545
+ <IslandWidget.Footer {...props}>
546
+ <IslandWidget.Footer.Content>
547
+ <Text tag="div" size={ETextSize.B3} type={EFontType.SECONDARY}>
548
+ <Link href="#">Link text</Link>
549
+ </Text>
550
+ </IslandWidget.Footer.Content>
551
+ <IslandWidget.Footer.Controls>
552
+ <Button theme={EButtonTheme.SECONDARY} size={EComponentSize.SM}>
553
+ Button text
554
+ </Button>
555
+ <Button theme={EButtonTheme.GENERAL} size={EComponentSize.SM}>
556
+ Button text
557
+ </Button>
558
+ </IslandWidget.Footer.Controls>
559
+ </IslandWidget.Footer>
560
+ )}
561
+ renderHeader={(props) => (
562
+ <IslandWidget.Header {...props}>
563
+ <IslandWidget.Header.Title>Title</IslandWidget.Header.Title>
564
+ <IslandWidget.Header.Description>Description</IslandWidget.Header.Description>
565
+ </IslandWidget.Header>
566
+ )}
567
+ />
568
+ <IslandWidget.ExtraFooter open={extraFooterOpen}>
569
+ <div className="island-widget-extra-footer">
570
+ <Text size={ETextSize.B3}>Extra footer content</Text>
571
+ </div>
572
+ </IslandWidget.ExtraFooter>
573
+ </IslandWidgetWrapper>
574
+ </>
575
+ );
576
+ };
577
+ ```
221
578
 
222
- ## Stories
579
+ ### WithoutFooterAndWithExtraFooter
223
580
 
224
- `stories/IslandWidget/IslandWidget.stories.tsx`
225
- Примеры: `stories/IslandWidget/examples/`
581
+ `stories/IslandWidget/examples/WithoutFooterAndWithExtraFooterExample.tsx`
226
582
 
227
- ---
583
+ ```tsx
584
+ import React, { useState } from "react";
585
+ import {
586
+ IslandWidget,
587
+ IslandWidgetWrapper,
588
+ Button,
589
+ EButtonTheme,
590
+ EComponentSize,
591
+ Text,
592
+ ETextSize,
593
+ } from "@sberbusiness/triplex-next";
594
+
595
+ export const WithoutFooterAndWithExtraFooterExample = () => {
596
+ const [extraFooterOpen, setExtraFooterOpen] = useState(false);
597
+ const [bodyHeight, setBodyHeight] = useState(260);
598
+
599
+ const handleExtraFooterOpen = (open: boolean) => {
600
+ setExtraFooterOpen(open);
601
+ setBodyHeight(open ? 196 : 260);
602
+ };
603
+
604
+ return (
605
+ <>
606
+ <div className="island-widget-control-panel">
607
+ <Button
608
+ theme={EButtonTheme.GENERAL}
609
+ size={EComponentSize.SM}
610
+ onClick={() => handleExtraFooterOpen(true)}
611
+ >
612
+ Open Extra Footer
613
+ </Button>
614
+ <Button
615
+ theme={EButtonTheme.GENERAL}
616
+ size={EComponentSize.SM}
617
+ onClick={() => handleExtraFooterOpen(false)}
618
+ >
619
+ Close Extra Footer
620
+ </Button>
621
+ </div>
622
+
623
+ <IslandWidgetWrapper>
624
+ <IslandWidget
625
+ renderBody={(props) => (
626
+ <IslandWidget.Body {...props}>
627
+ <div style={{ height: bodyHeight, transition: "height 0.3s ease-in-out" }}>Content</div>
628
+ </IslandWidget.Body>
629
+ )}
630
+ renderHeader={(props) => (
631
+ <IslandWidget.Header {...props}>
632
+ <IslandWidget.Header.Title>Title</IslandWidget.Header.Title>
633
+ <IslandWidget.Header.Description>Description</IslandWidget.Header.Description>
634
+ </IslandWidget.Header>
635
+ )}
636
+ />
637
+ <IslandWidget.ExtraFooter open={extraFooterOpen}>
638
+ <div className="island-widget-extra-footer">
639
+ <Text size={ETextSize.B3}>Extra footer content</Text>
640
+ </div>
641
+ </IslandWidget.ExtraFooter>
642
+ </IslandWidgetWrapper>
643
+ </>
644
+ );
645
+ };
646
+ ```
228
647
 
229
- ## История изменений
648
+ ### WithExtraFooterAndIslandWidgetHeight
649
+
650
+ `stories/IslandWidget/examples/WithExtraFooterAndIslandWidgetHeightExample.tsx`
651
+
652
+ ```tsx
653
+ import React, { useState } from "react";
654
+ import {
655
+ IslandWidget,
656
+ IslandWidgetWrapper,
657
+ Button,
658
+ EButtonTheme,
659
+ EComponentSize,
660
+ Text,
661
+ ETextSize,
662
+ } from "@sberbusiness/triplex-next";
663
+
664
+ export const WithExtraFooterAndIslandWidgetHeightExample = () => {
665
+ const [extraFooterOpen, setExtraFooterOpen] = useState(false);
666
+
667
+ return (
668
+ <>
669
+ <div className="island-widget-control-panel">
670
+ <Button theme={EButtonTheme.GENERAL} size={EComponentSize.SM} onClick={() => setExtraFooterOpen(true)}>
671
+ Open Extra Footer
672
+ </Button>
673
+ <Button theme={EButtonTheme.GENERAL} size={EComponentSize.SM} onClick={() => setExtraFooterOpen(false)}>
674
+ Close Extra Footer
675
+ </Button>
676
+ </div>
677
+
678
+ <IslandWidgetWrapper style={{ height: "500px" }}>
679
+ <IslandWidget
680
+ renderBody={(props) => <IslandWidget.Body {...props}>Content</IslandWidget.Body>}
681
+ renderHeader={(props) => (
682
+ <IslandWidget.Header {...props}>
683
+ <IslandWidget.Header.Title>Title</IslandWidget.Header.Title>
684
+ <IslandWidget.Header.Description>Description</IslandWidget.Header.Description>
685
+ </IslandWidget.Header>
686
+ )}
687
+ />
688
+ <IslandWidget.ExtraFooter open={extraFooterOpen}>
689
+ <div className="island-widget-extra-footer">
690
+ <Text size={ETextSize.B3}>Extra footer content</Text>
691
+ </div>
692
+ </IslandWidget.ExtraFooter>
693
+ </IslandWidgetWrapper>
694
+ </>
695
+ );
696
+ };
697
+ ```
230
698
 
231
- | Дата | Изменение |
232
- |---|---|
233
- | 2026-04-18 | Создан документ |