@sberbusiness/triplex-next-mcp-server 1.44.0-beta → 1.46.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/CardAction.md +615 -0
- package/bundle/components/Checkbox.md +32 -29
- package/bundle/components/CheckboxTreeExtended.md +9 -5
- package/bundle/components/Chip.md +51 -47
- package/bundle/components/ChipMonthYearPicker.md +344 -0
- package/bundle/components/ChipMultiselect.md +1251 -0
- package/bundle/components/ChipOptions.md +329 -0
- package/bundle/components/ChipSort.md +333 -0
- package/bundle/components/ChipSuggest.md +587 -0
- package/bundle/components/CodeText.md +26 -23
- package/bundle/components/Col.md +1 -2
- package/bundle/components/CollapsibleTree.md +8 -5
- package/bundle/components/DateField.md +222 -303
- package/bundle/components/DatePickerExtended.md +3 -1
- 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 +47 -42
- package/bundle/components/FormGroup.md +192 -52
- package/bundle/components/Header.md +491 -211
- package/bundle/components/HeaderPage.md +6 -2
- package/bundle/components/HelpBox.md +7 -1
- package/bundle/components/ImageGallery.md +33 -34
- package/bundle/components/ImageGalleryExtended.md +33 -34
- package/bundle/components/Island.md +16 -13
- package/bundle/components/IslandAccordion.md +443 -152
- package/bundle/components/IslandWidget.md +622 -157
- 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 +4 -5
- package/bundle/components/MasterTable.md +602 -0
- package/bundle/components/ModalWindow.md +7 -4
- package/bundle/components/MonthYearField.md +3 -1
- 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 +75 -17
- package/bundle/release-notes/1.45.0.mdx +117 -0
- package/bundle/release-notes/1.46.0.mdx +66 -0
- package/bundle/release-notes/1.47.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
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
---
|
|
2
2
|
component: IslandWidget
|
|
3
3
|
category: Layout
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
- Оптимизирован под работу в группе (`IslandWidgetWrapper` — provider для `hasExtraFooter`).
|
|
16
|
+
Виджет — карточка на `Island`, собранная из шапки, тела и подвала, которые задаются
|
|
17
|
+
render-функциями. Отличие от голого `Island` — своя схема отступов по размеру и сворачивание
|
|
18
|
+
контента в адаптиве: на узком экране виден только заголовок, остальное раскрывается по клику
|
|
19
|
+
по шапке.
|
|
21
20
|
|
|
22
|
-
Используй когда:
|
|
23
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
`
|
|
78
|
-
|
|
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
|
-
##
|
|
166
|
+
## Дизайн-токены
|
|
83
167
|
|
|
84
|
-
|
|
168
|
+
Переопределяются через `ThemeProvider` (prop `tokens`) — см. `ThemeProvider-ai.md` →
|
|
169
|
+
«Как переопределять токены». Значения по умолчанию —
|
|
170
|
+
`src/components/DesignTokens/components/IslandWidget.ts`.
|
|
85
171
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
|
|
177
|
+
Оба токена относятся к дополнительному подвалу: фон самого `ExtraFooter` и тень, которую
|
|
178
|
+
виджет рисует над ним, когда тот раскрыт. Фон и тень самой карточки — токены `Island`
|
|
179
|
+
(`Island.Type1_Background`, `Island.Type1_Shadow`), виджет их не переопределяет. Скругления
|
|
180
|
+
и отступы токенами не описываются — они заданы литералами в `styles/*.module.less`.
|
|
94
181
|
|
|
95
|
-
|
|
182
|
+
---
|
|
96
183
|
|
|
97
|
-
|
|
184
|
+
## Инварианты
|
|
98
185
|
|
|
99
|
-
|
|
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
|
-
|
|
217
|
+
---
|
|
102
218
|
|
|
103
|
-
|
|
219
|
+
## Accessibility
|
|
104
220
|
|
|
105
|
-
|
|
221
|
+
Сворачивание в адаптиве **не озвучено и не доступно с клавиатуры**: обработчик клика висит на
|
|
222
|
+
обычном `<div>`-обёртке вокруг шапки, у которой нет ни `role="button"`, ни `tabIndex`, ни
|
|
223
|
+
`aria-expanded` / `aria-controls`. Индикатор раскрытия — декоративная иконка с
|
|
224
|
+
`aria-hidden="true"`. Пользователь клавиатуры и скринридера на мобильной ширине не может
|
|
225
|
+
раскрыть контент.
|
|
106
226
|
|
|
107
|
-
|
|
227
|
+
Обходные пути до исправления:
|
|
228
|
+
- Передать `disableAdaptiveCollapsing`, если содержимое обязано быть доступным на всех ширинах.
|
|
229
|
+
- Положить собственную кнопку раскрытия в `IslandWidget.Header.Controls` нельзя — состояние
|
|
230
|
+
раскрытия внутреннее и снаружи не управляется.
|
|
108
231
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
232
|
+
Остальное — на потребителе:
|
|
233
|
+
- `IslandWidget.Header.Title` рендерит `Title` тегом по умолчанию; если карточке нужен
|
|
234
|
+
семантический заголовок уровня страницы, задай тег через props `Title` или положи свой `<h*>`.
|
|
235
|
+
- Кнопки-иконки в `Header.Controls` требуют собственного `aria-label` — библиотека
|
|
236
|
+
мультиязычная и текст не хардкодит.
|
|
237
|
+
- Роль и `aria-*` для карточки целиком передаются в `IslandWidget` через `...rest`.
|
|
112
238
|
|
|
113
|
-
|
|
114
|
-
- В `useEffect` пишет `setHasExtraFooter(open)` в `IslandWidgetLayoutContext` — корневой `IslandWidget` использует этот флаг для корректировки стилей корня (добавляет `.islandWidgetWithExtraFooter`).
|
|
239
|
+
---
|
|
115
240
|
|
|
116
|
-
|
|
241
|
+
## Связанные компоненты
|
|
117
242
|
|
|
118
|
-
|
|
243
|
+
### Альтернативы (в `related`)
|
|
119
244
|
|
|
120
|
-
|
|
245
|
+
- `IslandAccordion` — раскрывающиеся секции на `Island`. Путать легко: оба сворачивают контент
|
|
246
|
+
внутри карточки. Разница — у аккордеона раскрытие работает на всех ширинах и им управляет
|
|
247
|
+
потребитель, у виджета оно только в адаптиве и состояние внутреннее.
|
|
121
248
|
|
|
122
|
-
|
|
249
|
+
### Контракты по рендеру (в `related`)
|
|
123
250
|
|
|
124
|
-
|
|
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
|
-
|
|
127
|
-
const adaptive = useMatchMedia(
|
|
128
|
-
`(max-width: ${EScreenWidth.SM_MAX})`,
|
|
129
|
-
window.innerWidth <= parseInt(EScreenWidth.SM_MAX),
|
|
130
|
-
);
|
|
131
|
-
```
|
|
262
|
+
### Зависимости без отдельной связи (в `related` не входят)
|
|
132
263
|
|
|
133
|
-
-
|
|
134
|
-
|
|
264
|
+
- `MobileView` — виджет использует его хук `useMobileView()`, но сам компонент не рендерит.
|
|
265
|
+
Порог адаптива у них общий, менять его нужно синхронно.
|
|
135
266
|
|
|
136
|
-
###
|
|
267
|
+
### Части виджета (своего AI.md не имеют)
|
|
137
268
|
|
|
138
|
-
|
|
139
|
-
|
|
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
|
-
|
|
143
|
-
- `open` инициализируется значением `disableAdaptiveCollapsing`: без адаптивного сворачивания виджет открыт всегда (это защита, если `disableAdaptiveCollapsing` меняется рантайм).
|
|
144
|
-
- Нет prop'а `defaultOpen` / `opened` / `onToggle` — открытие полностью неуправляемое.
|
|
145
|
-
- Клик по заголовку → `handleHeaderClick` → `setOpen(!open)`. Срабатывает **только если adaptive**.
|
|
275
|
+
---
|
|
146
276
|
|
|
147
|
-
|
|
277
|
+
## Stories
|
|
148
278
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
? <ExpandAnimation expanded={open}>{renderContent()}</ExpandAnimation>
|
|
152
|
-
: <>{renderContent()}</>}
|
|
153
|
-
```
|
|
279
|
+
Основные истории: `stories/IslandWidget/IslandWidget.stories.tsx`
|
|
280
|
+
Файлы примеров: `stories/IslandWidget/examples/`
|
|
154
281
|
|
|
155
|
-
|
|
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
|
-
|
|
292
|
+
Файлы примеров с постфиксом `Example` — локальный legacy-паттерн папки.
|
|
158
293
|
|
|
159
|
-
|
|
160
|
-
<div
|
|
161
|
-
className={clsx({ [styles.islandWidgetHeaderWrapperAdaptive]: adaptive })}
|
|
162
|
-
onClick={adaptive ? handleHeaderClick : undefined}
|
|
163
|
-
>
|
|
164
|
-
{renderHeader({})}
|
|
165
|
-
</div>
|
|
166
|
-
```
|
|
294
|
+
---
|
|
167
295
|
|
|
168
|
-
|
|
169
|
-
- **Нет `role="button"`/`tabIndex`/keyboard-навигации** на wrapper'е — клик только мышью. Это a11y-gap.
|
|
296
|
+
## История изменений
|
|
170
297
|
|
|
171
|
-
|
|
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
|
-
|
|
174
|
-
<div className={clsx(styles.islandWidget, className, {
|
|
175
|
-
[styles.islandWidgetWithExtraFooter]: hasExtraFooter,
|
|
176
|
-
})}>
|
|
177
|
-
```
|
|
302
|
+
---
|
|
178
303
|
|
|
179
|
-
|
|
304
|
+
## Примеры использования
|
|
180
305
|
|
|
181
|
-
|
|
306
|
+
### Default
|
|
182
307
|
|
|
183
|
-
|
|
308
|
+
`stories/IslandWidget/examples/DefaultExample.tsx`
|
|
184
309
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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
|
-
|
|
378
|
+
`stories/IslandWidget/examples/SizesExample.tsx`
|
|
198
379
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
579
|
+
### WithoutFooterAndWithExtraFooter
|
|
223
580
|
|
|
224
|
-
`stories/IslandWidget/
|
|
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 | Создан документ |
|