@sberbusiness/triplex-next-mcp-server 0.24.0-beta1
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 +124 -0
- package/bundle/.gitkeep +0 -0
- package/bundle/components/AbstractTree.md +198 -0
- package/bundle/components/AccordionBase.md +227 -0
- package/bundle/components/Alert.md +317 -0
- package/bundle/components/Amount.md +225 -0
- package/bundle/components/AmountField.md +289 -0
- package/bundle/components/Avatar.md +209 -0
- package/bundle/components/Badge.md +277 -0
- package/bundle/components/Body.md +167 -0
- package/bundle/components/Button.md +230 -0
- package/bundle/components/ButtonDropdown.md +159 -0
- package/bundle/components/ButtonIcon.md +122 -0
- package/bundle/components/Calendar.md +445 -0
- package/bundle/components/Card.md +374 -0
- package/bundle/components/CarouselExtended.md +250 -0
- package/bundle/components/Checkbox.md +126 -0
- package/bundle/components/CheckboxTree.md +280 -0
- package/bundle/components/CheckboxTreeExtended.md +321 -0
- package/bundle/components/CheckboxXGroup.md +68 -0
- package/bundle/components/CheckboxYGroup.md +67 -0
- package/bundle/components/Chip.md +404 -0
- package/bundle/components/ChipGroup.md +179 -0
- package/bundle/components/Col.md +221 -0
- package/bundle/components/CollapsableTree.md +255 -0
- package/bundle/components/Confirm.md +343 -0
- package/bundle/components/DateField.md +371 -0
- package/bundle/components/DatePickerExtended.md +342 -0
- package/bundle/components/DateRange.md +275 -0
- package/bundle/components/DesignTokens.md +385 -0
- package/bundle/components/Divider.md +196 -0
- package/bundle/components/DocumentNumberEdit.md +280 -0
- package/bundle/components/Dropdown.md +433 -0
- package/bundle/components/Ellipsis.md +208 -0
- package/bundle/components/EmptyView.md +99 -0
- package/bundle/components/ExpandAnimation.md +281 -0
- package/bundle/components/Footer.md +339 -0
- package/bundle/components/FormField.md +326 -0
- package/bundle/components/FormGroup.md +99 -0
- package/bundle/components/Gap.md +113 -0
- package/bundle/components/Header.md +280 -0
- package/bundle/components/HelpBox.md +154 -0
- package/bundle/components/IconWrapper.md +145 -0
- package/bundle/components/Island.md +197 -0
- package/bundle/components/IslandAccordion.md +230 -0
- package/bundle/components/IslandWidget.md +233 -0
- package/bundle/components/KeyDownListener.md +157 -0
- package/bundle/components/LightBox.md +322 -0
- package/bundle/components/Link.md +161 -0
- package/bundle/components/List.md +247 -0
- package/bundle/components/ListMaster.md +204 -0
- package/bundle/components/Loader.md +161 -0
- package/bundle/components/LoaderScreen.md +134 -0
- package/bundle/components/Marker.md +127 -0
- package/bundle/components/MarkerStatus.md +148 -0
- package/bundle/components/MediaWidth.md +169 -0
- package/bundle/components/MobileView.md +120 -0
- package/bundle/components/ModalWindow.md +308 -0
- package/bundle/components/MonthYearField.md +168 -0
- package/bundle/components/MultiselectField.md +213 -0
- package/bundle/components/Notification.md +233 -0
- package/bundle/components/NumberField.md +178 -0
- package/bundle/components/OrderedList.md +169 -0
- package/bundle/components/Overlay.md +237 -0
- package/bundle/components/Page.md +246 -0
- package/bundle/components/Pagination.md +298 -0
- package/bundle/components/Portal.md +119 -0
- package/bundle/components/Radio.md +174 -0
- package/bundle/components/Row.md +125 -0
- package/bundle/components/SMSField.md +160 -0
- package/bundle/components/SegmentedControl.md +235 -0
- package/bundle/components/SelectExtendedField.md +322 -0
- package/bundle/components/SelectField.md +213 -0
- package/bundle/components/Skeleton.md +129 -0
- package/bundle/components/Slider.md +157 -0
- package/bundle/components/SliderExtended.md +210 -0
- package/bundle/components/SliderRange.md +160 -0
- package/bundle/components/SmallInput.md +101 -0
- package/bundle/components/Spoiler.md +121 -0
- package/bundle/components/StatusTracker.md +151 -0
- package/bundle/components/Step.md +121 -0
- package/bundle/components/Stepper.md +163 -0
- package/bundle/components/Suggest.md +160 -0
- package/bundle/components/SuggestField.md +157 -0
- package/bundle/components/SwipeableArea.md +144 -0
- package/bundle/components/Table.md +199 -0
- package/bundle/components/Tabs.md +152 -0
- package/bundle/components/TabsExtended.md +273 -0
- package/bundle/components/TabsLine.md +189 -0
- package/bundle/components/Tag.md +135 -0
- package/bundle/components/TagColor.md +121 -0
- package/bundle/components/TagGroup.md +100 -0
- package/bundle/components/TextField.md +152 -0
- package/bundle/components/TextareaField.md +124 -0
- package/bundle/components/ThemeProvider.md +181 -0
- package/bundle/components/Tooltip.md +254 -0
- package/bundle/components/TopOverlay.md +145 -0
- package/bundle/components/TreeView.md +224 -0
- package/bundle/components/Triggers.md +121 -0
- package/bundle/guides/codestyle.md +205 -0
- package/bundle/guides/commits.md +166 -0
- package/bundle/guides/context.md +277 -0
- package/bundle/guides/stories.md +618 -0
- package/bundle/guides/template.md +154 -0
- package/bundle/guides/tests.md +301 -0
- package/bundle/icons.json +4156 -0
- package/bundle/manifest.json +796 -0
- package/bundle/release-notes/1.1.0.mdx +26 -0
- package/bundle/release-notes/1.1.1.mdx +14 -0
- package/bundle/release-notes/1.10.0.mdx +43 -0
- package/bundle/release-notes/1.11.0.mdx +19 -0
- package/bundle/release-notes/1.12.0.mdx +21 -0
- package/bundle/release-notes/1.13.0.mdx +43 -0
- package/bundle/release-notes/1.14.0.mdx +29 -0
- package/bundle/release-notes/1.15.0.mdx +56 -0
- package/bundle/release-notes/1.16.0.mdx +55 -0
- package/bundle/release-notes/1.17.0.mdx +15 -0
- package/bundle/release-notes/1.18.0.mdx +24 -0
- package/bundle/release-notes/1.19.0.mdx +48 -0
- package/bundle/release-notes/1.19.1.mdx +9 -0
- package/bundle/release-notes/1.2.0.mdx +80 -0
- package/bundle/release-notes/1.20.0.mdx +48 -0
- package/bundle/release-notes/1.21.0.mdx +66 -0
- package/bundle/release-notes/1.22.0.mdx +75 -0
- package/bundle/release-notes/1.23.0.mdx +50 -0
- package/bundle/release-notes/1.24.0.mdx +59 -0
- package/bundle/release-notes/1.25.0.mdx +52 -0
- package/bundle/release-notes/1.26.0.mdx +11 -0
- package/bundle/release-notes/1.3.0.mdx +21 -0
- package/bundle/release-notes/1.4.0.mdx +37 -0
- package/bundle/release-notes/1.5.0.mdx +24 -0
- package/bundle/release-notes/1.6.0.mdx +37 -0
- package/bundle/release-notes/1.6.1.mdx +11 -0
- package/bundle/release-notes/1.7.0.mdx +29 -0
- package/bundle/release-notes/1.8.0.mdx +38 -0
- package/bundle/release-notes/1.9.0.mdx +28 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +324 -0
- package/dist/index.js.map +1 -0
- package/package.json +51 -0
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
---
|
|
2
|
+
component: IconWrapper
|
|
3
|
+
category: Icons
|
|
4
|
+
figma-node: ""
|
|
5
|
+
figma-file: ""
|
|
6
|
+
related: [ButtonIcon, Button, Link]
|
|
7
|
+
tokens: []
|
|
8
|
+
stories: stories/Icons/IconWrapper/IconWrapper.stories.tsx
|
|
9
|
+
version: "1.0"
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# IconWrapper
|
|
13
|
+
|
|
14
|
+
## Назначение
|
|
15
|
+
|
|
16
|
+
Минимальный `<span>`-обёртка для иконок из `@sberbusiness/icons-next`,
|
|
17
|
+
которая проставляет **глобальный CSS-класс `hoverable`** + опционально `active`/`disabled`.
|
|
18
|
+
Эти имена используются CSS-правилами из `icons-next/styles/icons.css` для
|
|
19
|
+
смены `fill` у палитры иконок при hover/active/disabled.
|
|
20
|
+
|
|
21
|
+
Без обёртки иконка имеет фиксированные цвета палитры. С обёрткой — реагирует
|
|
22
|
+
на состояния родителя.
|
|
23
|
+
|
|
24
|
+
Используй когда: нужно сделать кликабельную/интерактивную иконку вне
|
|
25
|
+
кнопки (например, в строке таблицы, в свободной вёрстке), либо когда
|
|
26
|
+
иконка внутри нестандартного контейнера должна менять цвет при hover/active.
|
|
27
|
+
Не используй когда: иконка внутри `ButtonIcon`/`Button` — там `hoverable`
|
|
28
|
+
уже навешен.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Файловая структура
|
|
33
|
+
|
|
34
|
+
```text
|
|
35
|
+
src/components/IconWrapper/
|
|
36
|
+
├── IconWrapper.tsx # forwardRef, <span>
|
|
37
|
+
├── index.ts # Barrel: IconWrapper, IIconWrapperProps
|
|
38
|
+
├── styles/IconWrapper.module.less # disableInteraction, displayContents (только)
|
|
39
|
+
└── __tests__/IconWrapper.test.tsx
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Варианты и props
|
|
45
|
+
|
|
46
|
+
### `IIconWrapperProps extends HTMLAttributes<HTMLSpanElement>`
|
|
47
|
+
|
|
48
|
+
| Prop | Тип | Default | Описание |
|
|
49
|
+
|---|---|---|---|
|
|
50
|
+
| `children` | `ReactNode` | — | Обычно `<SomeIcon16 />` из `@sberbusiness/icons-next`. |
|
|
51
|
+
| `active` | `boolean` | `false` | Принудительно применить стили active-цвета. Класс `active` (глобальный). |
|
|
52
|
+
| `disabled` | `boolean` | `false` | Приглушённый цвет. Класс `disabled` (глобальный). |
|
|
53
|
+
| `disableInteraction` | `boolean` | `false` | `pointer-events: none`. Иконка не ловит hover/click. Полезно, когда иконка внутри disabled-элемента, но сама не должна реагировать. |
|
|
54
|
+
| `displayContents` | `boolean` | `false` | `display: contents` — span не создаёт box'а. Для кейсов, когда обёртка мешает grid/flex лейауту. |
|
|
55
|
+
| `className`, `...HTMLSpanAttributes` | — | — | Стандартные. |
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Ключевые особенности реализации
|
|
60
|
+
|
|
61
|
+
### Глобальные классы `hoverable` / `active` / `disabled`
|
|
62
|
+
|
|
63
|
+
```tsx
|
|
64
|
+
className={clsx("hoverable", className, {
|
|
65
|
+
active: !!active,
|
|
66
|
+
disabled: !!disabled,
|
|
67
|
+
[styles.disableInteraction]: !!disableInteraction,
|
|
68
|
+
[styles.displayContents]: !!displayContents,
|
|
69
|
+
})}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
- `hoverable`, `active`, `disabled` — **не CSS-Modules классы**, а строковые литералы. Соответствующие правила живут в **`@sberbusiness/icons-next/styles/icons.css`** и выглядят как:
|
|
73
|
+
```css
|
|
74
|
+
@media (hover: hover) { .hoverable:hover ._cdqvp6 { fill: #21A19A; } ... }
|
|
75
|
+
.hoverable:not(:disabled):active ._cdqvp6, .hoverable.active ._cdqvp6 { fill: #007777; }
|
|
76
|
+
.hoverable:disabled ._cdqvp6, .hoverable.disabled ._cdqvp6 { fill: ... opacity: .15; }
|
|
77
|
+
```
|
|
78
|
+
- Правила написаны для каждого цветового класса палитры иконок (`._cdqvp6`, `._102z8qh` и т.д.). Переопределять их через свой CSS сложно — лучше использовать `paletteIndex` prop иконки.
|
|
79
|
+
- **Не переименовывать `"hoverable"` на camelCase или `styles.hoverable`** — это сломает работу CSS из icons-next.
|
|
80
|
+
|
|
81
|
+
### `disableInteraction` и `displayContents` — единственное, что у компонента есть в CSS
|
|
82
|
+
|
|
83
|
+
```less
|
|
84
|
+
.disableInteraction { pointer-events: none; }
|
|
85
|
+
.displayContents { display: contents; }
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Всё остальное визуальное поведение приходит от icons.css. Компонент — тонкая обёртка.
|
|
89
|
+
|
|
90
|
+
### `forwardRef` + typo: ref на `<span>`
|
|
91
|
+
|
|
92
|
+
`IIconWrapperProps` требует `children: React.ReactNode` как обязательный prop (в отличие от стандартного `HTMLAttributes`, где `children` опциональный).
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## Инварианты
|
|
97
|
+
|
|
98
|
+
- **Строковые классы `"hoverable"` / `"active"` / `"disabled"`** — кросс-пакетный контракт с `@sberbusiness/icons-next`. Не менять и не оборачивать в CSS-Modules.
|
|
99
|
+
- **`forwardRef`** — не убирать.
|
|
100
|
+
- **`children` обязателен** по типу — тонкая обёртка без контента бессмысленна.
|
|
101
|
+
- **`displayContents`** — используется там, где wrapper мешает layout'у родителя; если удаляешь, нужно проверить потребителей.
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Accessibility
|
|
106
|
+
|
|
107
|
+
- `<span>` без семантики. Если иконка кликабельна, **потребитель должен** добавить `role="button"`/`tabIndex`/`aria-label` через `...rest` **или лучше** использовать `ButtonIcon`.
|
|
108
|
+
- `disabled` prop — **чисто визуальный**, не делает элемент реально disabled (на `<span>` это невозможно). Для interaction-блокировки используй `disableInteraction`.
|
|
109
|
+
- `aria-hidden` — потребитель сам решает, нужно ли прятать иконку от скринридера (если она декоративная рядом с текстом — обычно да).
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Дизайн-токены
|
|
114
|
+
|
|
115
|
+
Нет собственных. Цвета иконок приходят из CSS-правил `@sberbusiness/icons-next`
|
|
116
|
+
по классам палитры (`_cdqvp6`, `_102z8qh`, ...), которые подставляются
|
|
117
|
+
компонентами иконок автоматически по `paletteIndex`.
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## Связанные компоненты
|
|
122
|
+
|
|
123
|
+
- `ButtonIcon` / `Button` — уже добавляют `hoverable` вокруг иконки. Использовать IconWrapper внутри них **не нужно**.
|
|
124
|
+
- `List` / `Table` — используют `IconWrapper` в служебных местах (сортировка, экспанд).
|
|
125
|
+
- `IslandAccordion` — использует прямую строку `"hoverable"` для chevron-иконки (тот же паттерн, без компонента).
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## Stories
|
|
130
|
+
|
|
131
|
+
`stories/Icons/IconWrapper/IconWrapper.stories.tsx`
|
|
132
|
+
Примеры: `stories/Icons/IconWrapper/examples/`
|
|
133
|
+
|
|
134
|
+
| Story | Example file | Что демонстрирует |
|
|
135
|
+
|---|---|---|
|
|
136
|
+
| `Default` | `DefaultExample.tsx` | Базовое использование с иконкой из icons-next. |
|
|
137
|
+
| `States` | `StatesExample.tsx` | `active` / `disabled` / `disableInteraction`. |
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## История изменений
|
|
142
|
+
|
|
143
|
+
| Дата | Изменение |
|
|
144
|
+
|---|---|
|
|
145
|
+
| 2026-04-18 | Создан документ |
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
---
|
|
2
|
+
component: Island
|
|
3
|
+
category: Layout
|
|
4
|
+
figma-node: ""
|
|
5
|
+
figma-file: ""
|
|
6
|
+
related: [IslandAccordion, IslandWidget, Card, Page]
|
|
7
|
+
tokens:
|
|
8
|
+
- --triplex-next-Island-Type1_Background
|
|
9
|
+
- --triplex-next-Island-Type1_Shadow
|
|
10
|
+
- --triplex-next-Island-Type2_Background
|
|
11
|
+
- --triplex-next-Island-Type2_Shadow
|
|
12
|
+
- --triplex-next-Island-Type3_Background
|
|
13
|
+
- --triplex-next-Island-Type3_Shadow
|
|
14
|
+
stories: stories/Island/Island.stories.tsx
|
|
15
|
+
version: "1.0"
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
# Island
|
|
19
|
+
|
|
20
|
+
## Назначение
|
|
21
|
+
|
|
22
|
+
Карточка-"остров" — контейнер со скруглениями, фоном и тенью. **Compound-компонент**
|
|
23
|
+
с тремя дочерними слотами: `Island.Header`, `Island.Body`, `Island.Footer`.
|
|
24
|
+
Имеет три визуальных типа (`TYPE_1` / `TYPE_2` / `TYPE_3`) — отличаются
|
|
25
|
+
цветом фона и тенью — и три размера (`SM`/`MD`/`LG`) — отличаются
|
|
26
|
+
радиусом, padding'ом и межблочным margin'ом.
|
|
27
|
+
|
|
28
|
+
Используй когда: нужна базовая карточка с заголовком, контентом и футером.
|
|
29
|
+
Для аккордеона предпочитай `IslandAccordion`, для виджета с иконкой —
|
|
30
|
+
`IslandWidget`.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Файловая структура
|
|
35
|
+
|
|
36
|
+
```text
|
|
37
|
+
src/components/Island/
|
|
38
|
+
├── Island.tsx # Object.assign → Body/Header/Footer
|
|
39
|
+
├── enums.ts # EIslandType (TYPE_1/TYPE_2/TYPE_3)
|
|
40
|
+
├── utils.ts # mapTypeToClassName(type) → styles.type1/2/3
|
|
41
|
+
├── index.ts # Barrel
|
|
42
|
+
├── components/
|
|
43
|
+
│ ├── IslandHeader.tsx # <div .islandHeader>
|
|
44
|
+
│ ├── IslandBody.tsx # <div .islandBody>
|
|
45
|
+
│ └── IslandFooter.tsx # <div .islandFooter>
|
|
46
|
+
└── styles/
|
|
47
|
+
├── Island.module.less # Типы, размеры, mobile-ресайз
|
|
48
|
+
├── IslandHeader.module.less
|
|
49
|
+
├── IslandBody.module.less
|
|
50
|
+
└── IslandFooter.module.less
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Compound-структура
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
<Island type={EIslandType.TYPE_1} size={EComponentSize.MD}>
|
|
59
|
+
<Island.Header>Заголовок</Island.Header>
|
|
60
|
+
<Island.Body>Основной контент</Island.Body>
|
|
61
|
+
<Island.Footer>
|
|
62
|
+
<Button>Действие</Button>
|
|
63
|
+
</Island.Footer>
|
|
64
|
+
</Island>
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Все три слота опциональны. Отступы между ними задаются смежным CSS-селектором
|
|
68
|
+
на корне:
|
|
69
|
+
```less
|
|
70
|
+
.islandHeader + .islandBody,
|
|
71
|
+
.islandHeader + .islandFooter,
|
|
72
|
+
.islandBody + .islandFooter { margin-top: <size>; }
|
|
73
|
+
```
|
|
74
|
+
Размер зазора меняется со `size` (8/12/16 px).
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Варианты и props
|
|
79
|
+
|
|
80
|
+
### Island — `IIslandProps extends HTMLAttributes<HTMLDivElement>`
|
|
81
|
+
|
|
82
|
+
| Prop | Тип | Default | Описание |
|
|
83
|
+
|---|---|---|---|
|
|
84
|
+
| `type` | `EIslandType` | `TYPE_1` | Визуальный тип (цвет фона + тень). |
|
|
85
|
+
| `size` | `EComponentSize` | `MD` | Размер — влияет на padding и border-radius. |
|
|
86
|
+
| `className`, `style`, `...HTMLDivAttributes` | — | — | Стандартные. |
|
|
87
|
+
|
|
88
|
+
### EIslandType
|
|
89
|
+
|
|
90
|
+
| Значение | Токены |
|
|
91
|
+
|---|---|
|
|
92
|
+
| `TYPE_1` | `--triplex-next-Island-Type1_Background` + `Type1_Shadow` |
|
|
93
|
+
| `TYPE_2` | `...Type2...` |
|
|
94
|
+
| `TYPE_3` | `...Type3...` |
|
|
95
|
+
|
|
96
|
+
### Размеры
|
|
97
|
+
|
|
98
|
+
| Size | Desktop (≥ sm+1) | Mobile (≤ @screen-sm-max) | Gap между слотами |
|
|
99
|
+
|---|---|---|---|
|
|
100
|
+
| `SM` | radius 16, padding 16 | radius 16, padding 16 | 8 |
|
|
101
|
+
| `MD` | radius 24, padding 24 | radius 16, padding 16 | 12 |
|
|
102
|
+
| `LG` | radius 32, padding 32 | radius 24, padding 24 | 16 |
|
|
103
|
+
|
|
104
|
+
На mobile `MD` уменьшается до SM-параметров; `LG` — до размеров близких к MD. Это сохранить при добавлении новых размеров.
|
|
105
|
+
|
|
106
|
+
### Дочерние — `IIslandHeaderProps`, `IIslandBodyProps`, `IIslandFooterProps`
|
|
107
|
+
|
|
108
|
+
Все extends `React.HTMLProps<HTMLDivElement>` (не `HTMLAttributes` — заметь разницу: `HTMLProps` шире, включает DOM-специфичные проп'ы вроде `form`, `crossOrigin`). Интерфейсы пустые, просто именованные alias'ы.
|
|
109
|
+
|
|
110
|
+
`IslandBody` merge'ит `className` через `clsx` — можно передавать свой класс.
|
|
111
|
+
|
|
112
|
+
**`IslandHeader` / `IslandFooter`** — НЕ merge'ят `className` из props (баг-или-легаси): `clsx(styles.islandHeader)` без пропагации `className`. **Если нужно передать свой класс — исправляй компонент.** Не полагайся на это поведение.
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## Ключевые особенности реализации
|
|
117
|
+
|
|
118
|
+
### Compound через `Object.assign`
|
|
119
|
+
|
|
120
|
+
```tsx
|
|
121
|
+
export const Island = Object.assign(
|
|
122
|
+
React.forwardRef<HTMLDivElement, IIslandProps>(...),
|
|
123
|
+
{ Body: IslandBody, Header: IslandHeader, Footer: IslandFooter },
|
|
124
|
+
);
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Margin между слотами — через смежный селектор
|
|
128
|
+
|
|
129
|
+
Отступы между Header/Body/Footer заданы на корне Island, а не на самих слотах. Это значит:
|
|
130
|
+
- если пропустить Body и написать `<Header /><Footer />` — между ними всё равно сработает `margin-top`.
|
|
131
|
+
- если вставить произвольный контент между слотами — смежный селектор не сработает, и придётся выставлять отступ вручную.
|
|
132
|
+
|
|
133
|
+
### Mobile resize
|
|
134
|
+
|
|
135
|
+
`@media (max-width: @screen-sm-max)` перекрывает padding/radius для всех размеров — карточка визуально уменьшается на мобильных. Это поведение реализует автоматическую адаптивность без дополнительных пропсов.
|
|
136
|
+
|
|
137
|
+
### `IslandHeader` / `IslandFooter` игнорируют `className`
|
|
138
|
+
|
|
139
|
+
В отличие от `IslandBody`, они не мержат `className`. Это по факту баг/неполная реализация, но изменение может затронуть потребителей — не исправлять без согласования.
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## Инварианты
|
|
144
|
+
|
|
145
|
+
- **`EIslandType` значения `TYPE_1`/`TYPE_2`/`TYPE_3`** — публичный API. Не переименовывать.
|
|
146
|
+
- **Compound через `Object.assign`** — `Island.Header`, `Island.Body`, `Island.Footer`. Barrel также экспортирует `IslandHeader`/`IslandBody`/`IslandFooter` напрямую.
|
|
147
|
+
- **Margin-логика через `+`-селектор** — изменение на gap-flex сломает существующие интеграции (padding/layout рассчитаны на margin-top).
|
|
148
|
+
- **`forwardRef`** — на всех четырёх компонентах.
|
|
149
|
+
- **Дефолт `type=TYPE_1`, `size=MD`** — не менять.
|
|
150
|
+
- **`HTMLProps<HTMLDivElement>`** (не `HTMLAttributes`) в интерфейсах слотов — сохранить, если потребитель опирается на расширенные атрибуты.
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## Accessibility
|
|
155
|
+
|
|
156
|
+
- `<div>`-контейнеры, без ARIA-ролей. Если остров используется как диалог/секция/регион — потребитель передаёт `role` и `aria-label` через `...rest`.
|
|
157
|
+
- `IslandHeader` — не `<header>`/`<h*>`; потребитель сам передаёт нужные семантические теги/заголовки внутрь.
|
|
158
|
+
- Tab-навигация — через интерактивные дети.
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## Дизайн-токены
|
|
163
|
+
|
|
164
|
+
```
|
|
165
|
+
--triplex-next-Island-Type1_Background
|
|
166
|
+
--triplex-next-Island-Type1_Shadow
|
|
167
|
+
--triplex-next-Island-Type2_Background
|
|
168
|
+
--triplex-next-Island-Type2_Shadow
|
|
169
|
+
--triplex-next-Island-Type3_Background
|
|
170
|
+
--triplex-next-Island-Type3_Shadow
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Размеры (`16/24/32` px) и gap'ы (`8/12/16` px) заданы литералами.
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## Связанные компоненты
|
|
178
|
+
|
|
179
|
+
- `IslandAccordion` — остров с раскрытием (содержит `hoverable` chevron-иконку).
|
|
180
|
+
- `IslandWidget` — остров-виджет с иконкой и действиями.
|
|
181
|
+
- `Card` — другая карточная абстракция (интерактивная, с hover-состояниями).
|
|
182
|
+
- `Page` — типовой контейнер страницы, часто содержит Island'ы.
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## Stories
|
|
187
|
+
|
|
188
|
+
`stories/Island/Island.stories.tsx`
|
|
189
|
+
Примеры: `stories/Island/examples/`
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## История изменений
|
|
194
|
+
|
|
195
|
+
| Дата | Изменение |
|
|
196
|
+
|---|---|
|
|
197
|
+
| 2026-04-18 | Создан документ |
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
---
|
|
2
|
+
component: IslandAccordion
|
|
3
|
+
category: Layout
|
|
4
|
+
figma-node: ""
|
|
5
|
+
figma-file: ""
|
|
6
|
+
related: [Island, IslandWidget, ExpandAnimation, Step, Button]
|
|
7
|
+
tokens: []
|
|
8
|
+
stories: stories/IslandAccordion/IslandAccordion.stories.tsx
|
|
9
|
+
version: "1.0"
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# IslandAccordion
|
|
13
|
+
|
|
14
|
+
## Назначение
|
|
15
|
+
|
|
16
|
+
Аккордеон — список раскрывающихся `Island`-карточек. Каждая `Item` хранит
|
|
17
|
+
собственное открытое состояние (либо управляется извне через `opened`),
|
|
18
|
+
анимация раскрытия делегирована `ExpandAnimation`. Поддерживает step-индикатор
|
|
19
|
+
(через `Step`), кнопку удаления (`onRemove`), статусы и размеры (из `EComponentSize`)
|
|
20
|
+
и типы (из `EIslandType`) — оба прокидываются детям через контекст.
|
|
21
|
+
|
|
22
|
+
Используй когда: нужен последовательный список шагов/секций с раскрытием
|
|
23
|
+
(wizard, FAQ-with-steps). Без step-индикатора — тоже валидный кейс.
|
|
24
|
+
Не используй когда: нужен "плоский" аккордеон без визуала карточек —
|
|
25
|
+
возьми кастомное решение или составь из `ExpandAnimation` напрямую.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Файловая структура
|
|
30
|
+
|
|
31
|
+
```text
|
|
32
|
+
src/components/IslandAccordion/
|
|
33
|
+
├── IslandAccordion.tsx # <ul>, Provider контекста (size/type), Object.assign → Item
|
|
34
|
+
├── IslandAccordionContext.ts # IIslandAccordionContext = { size, type }
|
|
35
|
+
├── index.ts # Barrel
|
|
36
|
+
├── components/
|
|
37
|
+
│ ├── IslandAccordionItem.tsx # <li>, Island.Header с кнопкой + ExpandAnimation + Step/Remove
|
|
38
|
+
│ ├── IslandAccordionContent.tsx # Тонкая обёртка над Island.Body
|
|
39
|
+
│ ├── IslandAccordionFooter.tsx # Тонкая обёртка над Island.Footer
|
|
40
|
+
│ └── index.ts
|
|
41
|
+
└── styles/IslandAccordion.module.less
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Compound-структура
|
|
47
|
+
|
|
48
|
+
```tsx
|
|
49
|
+
<IslandAccordion size={EComponentSize.MD} type={EIslandType.TYPE_1}>
|
|
50
|
+
<IslandAccordion.Item
|
|
51
|
+
id="step-1"
|
|
52
|
+
title="Шаг первый"
|
|
53
|
+
num={1}
|
|
54
|
+
status={EStepStatus.COMPLETED}
|
|
55
|
+
opened={isFirstOpen}
|
|
56
|
+
onToggle={(next, id) => setFirstOpen(next)}
|
|
57
|
+
onRemove={(id) => removeStep(id)}
|
|
58
|
+
>
|
|
59
|
+
<IslandAccordionContent>
|
|
60
|
+
{/* ... произвольный контент шага ... */}
|
|
61
|
+
</IslandAccordionContent>
|
|
62
|
+
<IslandAccordionFooter>
|
|
63
|
+
<Button>Следующий шаг</Button>
|
|
64
|
+
</IslandAccordionFooter>
|
|
65
|
+
</IslandAccordion.Item>
|
|
66
|
+
</IslandAccordion>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Варианты и props
|
|
72
|
+
|
|
73
|
+
### IslandAccordion — `IIslandAccordionProps extends HTMLAttributes<HTMLUListElement>, Pick<IIslandProps, "type">`
|
|
74
|
+
|
|
75
|
+
| Prop | Тип | Default | Описание |
|
|
76
|
+
|---|---|---|---|
|
|
77
|
+
| `size` | `EComponentSize` | `MD` | Передаётся детям через контекст. |
|
|
78
|
+
| `type` | `EIslandType` | `TYPE_1` | Передаётся детям через контекст. |
|
|
79
|
+
| `...ulAttributes` | — | — | Обычные `<ul>`-атрибуты. |
|
|
80
|
+
|
|
81
|
+
### IslandAccordion.Item — `IIslandAccordionItemProps extends Omit<HTMLAttributes<HTMLLIElement>, "title">`
|
|
82
|
+
|
|
83
|
+
| Prop | Тип | Default | Описание |
|
|
84
|
+
|---|---|---|---|
|
|
85
|
+
| `title` | `ReactNode` | — | Заголовок. Обязателен. |
|
|
86
|
+
| `id` | `string` | — | Уникальный id (обязателен, используется как ключ и для ARIA). |
|
|
87
|
+
| `num` | `number` | — | Цифра в кружке Step. Применяется только вместе со `status`. |
|
|
88
|
+
| `status` | `EStepStatus` | — | Статус шага; рендерит `Step` при наличии `num`. |
|
|
89
|
+
| `stepHint` | `string` | — | Подсказка рядом с цифрой. Игнорируется в `disabled`. |
|
|
90
|
+
| `opened` | `boolean` | — (uncontrolled) | Управляемое состояние раскрытия. Если не передан — item uncontrolled, хранит своё состояние через `useState`. |
|
|
91
|
+
| `disabled` | `boolean` | `false` | Блокирует клик по заголовку и удаление. |
|
|
92
|
+
| `onToggle` | `(newOpened, id) => void` | — | Срабатывает даже в disabled? Нет — ранний return при `disabled`. |
|
|
93
|
+
| `onRemove` | `(id) => void` | — | Рендерит крестик справа; при клике вызывает `onRemove(id)`. |
|
|
94
|
+
| `transitionProps` | `IExpandAnimationProps["transitionProps"]` | — | Проброс в `ExpandAnimation`. |
|
|
95
|
+
|
|
96
|
+
### IslandAccordion.Item.Content / Footer
|
|
97
|
+
|
|
98
|
+
Тонкие обёртки над `Island.Body` / `Island.Footer` с добавлением нужных
|
|
99
|
+
отступов (`styles.body`, `styles.footer`).
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Ключевые особенности реализации
|
|
104
|
+
|
|
105
|
+
### Controlled / uncontrolled по `opened`
|
|
106
|
+
|
|
107
|
+
- Если `opened !== undefined` — controlled; `useEffect` синхронизирует
|
|
108
|
+
внутренний `isOpen` с `opened` при изменении.
|
|
109
|
+
- Если `opened === undefined` — uncontrolled; `handleHeaderClick` переключает
|
|
110
|
+
локальный `isOpen` через `setIsOpen(newOpened)`.
|
|
111
|
+
- В обоих случаях вызывается `onToggle(newOpened, id)`.
|
|
112
|
+
|
|
113
|
+
### Размеры через маппинги
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
SIZE_TO_TITLE_SIZE_MAP = { SM: H3, MD: H3, LG: H2 } // Title внутри заголовка
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Размер `size` также влияет на общий layout через CSS-классы (`.sm`, `.md`, `.lg`).
|
|
120
|
+
|
|
121
|
+
### Типы через локальный map
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
TYPE_TO_CLASS_NAME_MAP = { TYPE_1: styles.type1, TYPE_2: styles.type2, TYPE_3: styles.type3 }
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Дублирует `Island.utils.mapTypeToClassName`, но использует **локальные** стили
|
|
128
|
+
аккордеона (в `IslandAccordion.module.less`), а не Island'овские.
|
|
129
|
+
|
|
130
|
+
### Чип `hoverable` на свёрнутом заголовке
|
|
131
|
+
|
|
132
|
+
```tsx
|
|
133
|
+
className={clsx(styles.header, { hoverable: !isOpen })}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Когда item закрыт — иконка chevron реагирует на hover (меняет цвет через
|
|
137
|
+
правила из `@sberbusiness/icons-next/styles/icons.css`). Когда открыт —
|
|
138
|
+
класс не ставится, иконка статична.
|
|
139
|
+
|
|
140
|
+
### ARIA-связка через `uniqueId`
|
|
141
|
+
|
|
142
|
+
- `instanceId` генерируется через `useRef(uniqueId()).current` — НЕ SSR-safe.
|
|
143
|
+
Для SSR-сценариев потребителю нужно передавать стабильный `id`.
|
|
144
|
+
- `aria-expanded`, `aria-controls` (кнопка → контент), `role="region"`,
|
|
145
|
+
`aria-labelledby` (контент → кнопка) проставляются.
|
|
146
|
+
- Контент рендерится внутри `ExpandAnimation` с этими ARIA-атрибутами;
|
|
147
|
+
даже в свёрнутом состоянии `ExpandAnimation` может оставлять элемент в DOM
|
|
148
|
+
(зависит от реализации) — не полагайся на "нет DOM = нет фокусируемых".
|
|
149
|
+
|
|
150
|
+
### Кнопка удаления — снаружи карточки
|
|
151
|
+
|
|
152
|
+
```tsx
|
|
153
|
+
{onRemove && (
|
|
154
|
+
<span className={styles.remove}>
|
|
155
|
+
<ButtonIcon onClick={handleRemoveClick} disabled={disabled} title="Удалить">
|
|
156
|
+
<CrossStrokeSrvIcon24 paletteIndex={5} />
|
|
157
|
+
</ButtonIcon>
|
|
158
|
+
</span>
|
|
159
|
+
)}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Это **хардкод русского текста `title="Удалить"`** — нарушение правил
|
|
163
|
+
мультиязычности (`aria-label`/`title` не должны быть на конкретном языке).
|
|
164
|
+
**Technical debt.** Кандидат на рефакторинг: вынести в prop `removeTitle`
|
|
165
|
+
или полностью убрать, оставив потребителю.
|
|
166
|
+
|
|
167
|
+
### `// eslint-disable-next-line react-hooks/refs`
|
|
168
|
+
|
|
169
|
+
```ts
|
|
170
|
+
const instanceId = useRef(uniqueId()).current;
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Паттерн создаёт id один раз на монтировании. ESLint ругается, но это
|
|
174
|
+
осознанный legacy-подход; в новом коде лучше `React.useId()`.
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## Инварианты
|
|
179
|
+
|
|
180
|
+
- **`IslandAccordionContext` shape** (`size`, `type`) — при изменении подхватят все items; передать извне только `type` и `size` — контекст public.
|
|
181
|
+
- **`Object.assign` на `IslandAccordion` → `Item`**, на `Item` → `Content`/`Footer`. Двухуровневый compound.
|
|
182
|
+
- **`<ul>` / `<li>`** — семантика списка, её нельзя заменить на `<div>` без согласования (ломает скринридеры и существующую стилизацию).
|
|
183
|
+
- **`onRemove` title="Удалить"** — захардкоженный текст. Не дублировать паттерн в других компонентах; при переводе библиотеки — первая точка исправления.
|
|
184
|
+
- **`forwardRef`** на корне и на `Item`; `Content`/`Footer` — `React.FC`.
|
|
185
|
+
- **`opened` управляемое/неуправляемое поведение** — не нарушать (ломает интеграции).
|
|
186
|
+
- **`id` обязателен** на Item — используется как React key и в ARIA.
|
|
187
|
+
- **`uniqueId` из lodash** — не SSR-safe; потребители для SSR должны передавать `id` явно.
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## Accessibility
|
|
192
|
+
|
|
193
|
+
- **`<button>`-заголовок** — полная клавиатурная навигация (Tab, Enter, Space).
|
|
194
|
+
- `aria-expanded` на заголовке, `aria-controls` → id контента.
|
|
195
|
+
- `role="region"` + `aria-labelledby` на контенте — скринридер объявляет секцию при входе.
|
|
196
|
+
- `disabled` — нативный HTML-атрибут на `<button>` + класс `.disabled` для CSS.
|
|
197
|
+
- **Иконка chevron** — `aria-hidden="true"`, правильно (вся семантика на кнопке).
|
|
198
|
+
- **Кнопка удаления** — title="Удалить" (см. debt выше). Для корректной мультиязычности потребитель должен переопределить через передаваемые пропы в будущем или не использовать `onRemove`.
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## Дизайн-токены
|
|
203
|
+
|
|
204
|
+
Нет прямых — цвет/тень приходят от `Island` (`--triplex-next-Island-*`).
|
|
205
|
+
Chevron-цвета — из `@sberbusiness/icons-next` (palette 5).
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## Связанные компоненты
|
|
210
|
+
|
|
211
|
+
- `Island` — корневая карточка каждого Item.
|
|
212
|
+
- `ExpandAnimation` — раскрытие контента.
|
|
213
|
+
- `Step` — номер+статус в заголовке.
|
|
214
|
+
- `Typography.Title` — заголовок.
|
|
215
|
+
- `ButtonIcon` — кнопка удаления.
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
## Stories
|
|
220
|
+
|
|
221
|
+
`stories/IslandAccordion/IslandAccordion.stories.tsx`
|
|
222
|
+
Примеры: `stories/IslandAccordion/examples/`
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
## История изменений
|
|
227
|
+
|
|
228
|
+
| Дата | Изменение |
|
|
229
|
+
|---|---|
|
|
230
|
+
| 2026-04-18 | Создан документ |
|