@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.
Files changed (140) hide show
  1. package/README.md +124 -0
  2. package/bundle/.gitkeep +0 -0
  3. package/bundle/components/AbstractTree.md +198 -0
  4. package/bundle/components/AccordionBase.md +227 -0
  5. package/bundle/components/Alert.md +317 -0
  6. package/bundle/components/Amount.md +225 -0
  7. package/bundle/components/AmountField.md +289 -0
  8. package/bundle/components/Avatar.md +209 -0
  9. package/bundle/components/Badge.md +277 -0
  10. package/bundle/components/Body.md +167 -0
  11. package/bundle/components/Button.md +230 -0
  12. package/bundle/components/ButtonDropdown.md +159 -0
  13. package/bundle/components/ButtonIcon.md +122 -0
  14. package/bundle/components/Calendar.md +445 -0
  15. package/bundle/components/Card.md +374 -0
  16. package/bundle/components/CarouselExtended.md +250 -0
  17. package/bundle/components/Checkbox.md +126 -0
  18. package/bundle/components/CheckboxTree.md +280 -0
  19. package/bundle/components/CheckboxTreeExtended.md +321 -0
  20. package/bundle/components/CheckboxXGroup.md +68 -0
  21. package/bundle/components/CheckboxYGroup.md +67 -0
  22. package/bundle/components/Chip.md +404 -0
  23. package/bundle/components/ChipGroup.md +179 -0
  24. package/bundle/components/Col.md +221 -0
  25. package/bundle/components/CollapsableTree.md +255 -0
  26. package/bundle/components/Confirm.md +343 -0
  27. package/bundle/components/DateField.md +371 -0
  28. package/bundle/components/DatePickerExtended.md +342 -0
  29. package/bundle/components/DateRange.md +275 -0
  30. package/bundle/components/DesignTokens.md +385 -0
  31. package/bundle/components/Divider.md +196 -0
  32. package/bundle/components/DocumentNumberEdit.md +280 -0
  33. package/bundle/components/Dropdown.md +433 -0
  34. package/bundle/components/Ellipsis.md +208 -0
  35. package/bundle/components/EmptyView.md +99 -0
  36. package/bundle/components/ExpandAnimation.md +281 -0
  37. package/bundle/components/Footer.md +339 -0
  38. package/bundle/components/FormField.md +326 -0
  39. package/bundle/components/FormGroup.md +99 -0
  40. package/bundle/components/Gap.md +113 -0
  41. package/bundle/components/Header.md +280 -0
  42. package/bundle/components/HelpBox.md +154 -0
  43. package/bundle/components/IconWrapper.md +145 -0
  44. package/bundle/components/Island.md +197 -0
  45. package/bundle/components/IslandAccordion.md +230 -0
  46. package/bundle/components/IslandWidget.md +233 -0
  47. package/bundle/components/KeyDownListener.md +157 -0
  48. package/bundle/components/LightBox.md +322 -0
  49. package/bundle/components/Link.md +161 -0
  50. package/bundle/components/List.md +247 -0
  51. package/bundle/components/ListMaster.md +204 -0
  52. package/bundle/components/Loader.md +161 -0
  53. package/bundle/components/LoaderScreen.md +134 -0
  54. package/bundle/components/Marker.md +127 -0
  55. package/bundle/components/MarkerStatus.md +148 -0
  56. package/bundle/components/MediaWidth.md +169 -0
  57. package/bundle/components/MobileView.md +120 -0
  58. package/bundle/components/ModalWindow.md +308 -0
  59. package/bundle/components/MonthYearField.md +168 -0
  60. package/bundle/components/MultiselectField.md +213 -0
  61. package/bundle/components/Notification.md +233 -0
  62. package/bundle/components/NumberField.md +178 -0
  63. package/bundle/components/OrderedList.md +169 -0
  64. package/bundle/components/Overlay.md +237 -0
  65. package/bundle/components/Page.md +246 -0
  66. package/bundle/components/Pagination.md +298 -0
  67. package/bundle/components/Portal.md +119 -0
  68. package/bundle/components/Radio.md +174 -0
  69. package/bundle/components/Row.md +125 -0
  70. package/bundle/components/SMSField.md +160 -0
  71. package/bundle/components/SegmentedControl.md +235 -0
  72. package/bundle/components/SelectExtendedField.md +322 -0
  73. package/bundle/components/SelectField.md +213 -0
  74. package/bundle/components/Skeleton.md +129 -0
  75. package/bundle/components/Slider.md +157 -0
  76. package/bundle/components/SliderExtended.md +210 -0
  77. package/bundle/components/SliderRange.md +160 -0
  78. package/bundle/components/SmallInput.md +101 -0
  79. package/bundle/components/Spoiler.md +121 -0
  80. package/bundle/components/StatusTracker.md +151 -0
  81. package/bundle/components/Step.md +121 -0
  82. package/bundle/components/Stepper.md +163 -0
  83. package/bundle/components/Suggest.md +160 -0
  84. package/bundle/components/SuggestField.md +157 -0
  85. package/bundle/components/SwipeableArea.md +144 -0
  86. package/bundle/components/Table.md +199 -0
  87. package/bundle/components/Tabs.md +152 -0
  88. package/bundle/components/TabsExtended.md +273 -0
  89. package/bundle/components/TabsLine.md +189 -0
  90. package/bundle/components/Tag.md +135 -0
  91. package/bundle/components/TagColor.md +121 -0
  92. package/bundle/components/TagGroup.md +100 -0
  93. package/bundle/components/TextField.md +152 -0
  94. package/bundle/components/TextareaField.md +124 -0
  95. package/bundle/components/ThemeProvider.md +181 -0
  96. package/bundle/components/Tooltip.md +254 -0
  97. package/bundle/components/TopOverlay.md +145 -0
  98. package/bundle/components/TreeView.md +224 -0
  99. package/bundle/components/Triggers.md +121 -0
  100. package/bundle/guides/codestyle.md +205 -0
  101. package/bundle/guides/commits.md +166 -0
  102. package/bundle/guides/context.md +277 -0
  103. package/bundle/guides/stories.md +618 -0
  104. package/bundle/guides/template.md +154 -0
  105. package/bundle/guides/tests.md +301 -0
  106. package/bundle/icons.json +4156 -0
  107. package/bundle/manifest.json +796 -0
  108. package/bundle/release-notes/1.1.0.mdx +26 -0
  109. package/bundle/release-notes/1.1.1.mdx +14 -0
  110. package/bundle/release-notes/1.10.0.mdx +43 -0
  111. package/bundle/release-notes/1.11.0.mdx +19 -0
  112. package/bundle/release-notes/1.12.0.mdx +21 -0
  113. package/bundle/release-notes/1.13.0.mdx +43 -0
  114. package/bundle/release-notes/1.14.0.mdx +29 -0
  115. package/bundle/release-notes/1.15.0.mdx +56 -0
  116. package/bundle/release-notes/1.16.0.mdx +55 -0
  117. package/bundle/release-notes/1.17.0.mdx +15 -0
  118. package/bundle/release-notes/1.18.0.mdx +24 -0
  119. package/bundle/release-notes/1.19.0.mdx +48 -0
  120. package/bundle/release-notes/1.19.1.mdx +9 -0
  121. package/bundle/release-notes/1.2.0.mdx +80 -0
  122. package/bundle/release-notes/1.20.0.mdx +48 -0
  123. package/bundle/release-notes/1.21.0.mdx +66 -0
  124. package/bundle/release-notes/1.22.0.mdx +75 -0
  125. package/bundle/release-notes/1.23.0.mdx +50 -0
  126. package/bundle/release-notes/1.24.0.mdx +59 -0
  127. package/bundle/release-notes/1.25.0.mdx +52 -0
  128. package/bundle/release-notes/1.26.0.mdx +11 -0
  129. package/bundle/release-notes/1.3.0.mdx +21 -0
  130. package/bundle/release-notes/1.4.0.mdx +37 -0
  131. package/bundle/release-notes/1.5.0.mdx +24 -0
  132. package/bundle/release-notes/1.6.0.mdx +37 -0
  133. package/bundle/release-notes/1.6.1.mdx +11 -0
  134. package/bundle/release-notes/1.7.0.mdx +29 -0
  135. package/bundle/release-notes/1.8.0.mdx +38 -0
  136. package/bundle/release-notes/1.9.0.mdx +28 -0
  137. package/dist/index.d.ts +2 -0
  138. package/dist/index.js +324 -0
  139. package/dist/index.js.map +1 -0
  140. 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 | Создан документ |