@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,12 @@
|
|
|
1
1
|
---
|
|
2
2
|
component: IslandAccordion
|
|
3
3
|
category: Layout
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
related: [Island, IslandWidget, ExpandAnimation, Step, ButtonIcon]
|
|
5
|
+
tokens:
|
|
6
|
+
- IslandAccordion.Type1_Header_Background_Hover
|
|
7
|
+
- IslandAccordion.Type2_Header_Background_Hover
|
|
8
|
+
- IslandAccordion.Type3_Header_Background_Hover
|
|
9
|
+
- IslandAccordion.Shadow_Focus
|
|
8
10
|
stories: stories/IslandAccordion/IslandAccordion.stories.tsx
|
|
9
11
|
version: "1.0"
|
|
10
12
|
---
|
|
@@ -13,213 +15,255 @@ version: "1.0"
|
|
|
13
15
|
|
|
14
16
|
## Назначение
|
|
15
17
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
(через `Step`), кнопку удаления (`onRemove`), статусы и размеры (из `EComponentSize`)
|
|
20
|
-
и типы (из `EIslandType`) — оба прокидываются детям через контекст.
|
|
18
|
+
Список раскрывающихся карточек: `<ul>`, каждый элемент которого — `<li>` с карточкой `Island`,
|
|
19
|
+
кнопкой-заголовком и анимированно раскрываемым содержимым. Элемент дополнительно умеет
|
|
20
|
+
показывать номер шага со статусом (`Step`) и кнопку удаления.
|
|
21
21
|
|
|
22
|
-
Используй когда:
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
возьми кастомное решение или составь из `ExpandAnimation` напрямую.
|
|
22
|
+
Используй когда: набор логически сгруппированных блоков нужно уместить в ограниченном
|
|
23
|
+
пространстве и дать пользователю раскрывать их по одному — шаги оформления заявки, разделы
|
|
24
|
+
формы, FAQ.
|
|
26
25
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
```
|
|
26
|
+
Не используй когда:
|
|
27
|
+
- Нужна одна нераскрывающаяся карточка — возьми `Island`.
|
|
28
|
+
- Нужен виджет, который сворачивается только в адаптиве, а не по клику пользователя — возьми
|
|
29
|
+
`IslandWidget`.
|
|
30
|
+
- Нужны вкладки (виден ровно один раздел, заголовки в ряд) — возьми `Tabs`.
|
|
31
|
+
- Нужна анимация раскрытия сама по себе, без карточки и заголовка — возьми `ExpandAnimation`.
|
|
32
|
+
- Нужно дерево с раскрывающимися узлами — возьми `CollapsibleTree`.
|
|
43
33
|
|
|
44
34
|
---
|
|
45
35
|
|
|
46
|
-
##
|
|
36
|
+
## Состав
|
|
47
37
|
|
|
48
38
|
```tsx
|
|
49
|
-
<IslandAccordion size
|
|
50
|
-
<IslandAccordion.Item
|
|
51
|
-
|
|
52
|
-
|
|
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>
|
|
39
|
+
<IslandAccordion size type> // <ul>, задаёт size и type через контекст
|
|
40
|
+
<IslandAccordion.Item id title> // <li> + Island + кнопка-заголовок + ExpandAnimation
|
|
41
|
+
<IslandAccordion.Item.Content> // Island.Body внутри ExpandAnimation
|
|
42
|
+
<IslandAccordion.Item.Footer> // Island.Footer внутри ExpandAnimation
|
|
67
43
|
```
|
|
68
44
|
|
|
45
|
+
Из barrel экспортируются и «плоские» имена: `IslandAccordionItem`, `IslandAccordionContent`,
|
|
46
|
+
`IslandAccordionFooter` — вместе с точечными псевдонимами это один и тот же компонент.
|
|
47
|
+
|
|
48
|
+
`Content` и `Footer` необязательны: содержимым `Item` может быть произвольный узел, он попадёт
|
|
49
|
+
внутрь `ExpandAnimation` как есть. Но тогда на него не подействуют внутренние отступы аккордеона
|
|
50
|
+
(они заданы на `.body` / `.footer`).
|
|
51
|
+
|
|
69
52
|
---
|
|
70
53
|
|
|
71
54
|
## Варианты и props
|
|
72
55
|
|
|
73
|
-
### IslandAccordion
|
|
74
|
-
|
|
75
|
-
| Prop | Тип | Default | Описание |
|
|
76
|
-
|---|---|---|---|
|
|
77
|
-
| `size` | `EComponentSize` | `MD` | Передаётся детям через контекст. |
|
|
78
|
-
| `type` | `EIslandType` | `TYPE_1` | Передаётся детям через контекст. |
|
|
79
|
-
| `...ulAttributes` | — | — | Обычные `<ul>`-атрибуты. |
|
|
56
|
+
### IslandAccordion
|
|
80
57
|
|
|
81
|
-
|
|
58
|
+
Обязательных props нет.
|
|
82
59
|
|
|
83
|
-
| Prop | Тип |
|
|
60
|
+
| Prop | Тип | По умолчанию | Описание |
|
|
84
61
|
|---|---|---|---|
|
|
85
|
-
| `
|
|
86
|
-
| `
|
|
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`).
|
|
62
|
+
| `size` | `EComponentSize` | `EComponentSize.MD` | Размер всех элементов: паддинги, скругление, отступ между элементами, размер заголовка |
|
|
63
|
+
| `type` | `EIslandType` | `EIslandType.TYPE_1` | Визуальный тип карточки `Island` у элементов; влияет и на цвет hover заголовка |
|
|
100
64
|
|
|
101
|
-
|
|
65
|
+
Расширяет `React.HTMLAttributes<HTMLUListElement>` — `className`, `style`, `aria-*`, `data-*`
|
|
66
|
+
и обработчики попадают на корневой `<ul>`.
|
|
102
67
|
|
|
103
|
-
|
|
68
|
+
Обе props раздаются элементам **через контекст**, а не через клонирование детей, поэтому между
|
|
69
|
+
`IslandAccordion` и его `Item` можно вставлять произвольные обёртки. Обратная сторона: `Item`,
|
|
70
|
+
отрендеренный вне `IslandAccordion`, берёт значения по умолчанию из
|
|
71
|
+
`initialIslandAccordionContext` (`MD` + `TYPE_1`) и не ломается.
|
|
104
72
|
|
|
105
|
-
###
|
|
73
|
+
### IslandAccordion.Item
|
|
106
74
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
75
|
+
| Prop | Тип | По умолчанию | Описание |
|
|
76
|
+
|---|---|---|---|
|
|
77
|
+
| `title` | `React.ReactNode` | — | **Обязательный.** Содержимое заголовка. Оборачивается в `Title` с `tag="div"` |
|
|
78
|
+
| `id` | `string` | — | **Обязательный.** Проставляется на корневой `<li>` и передаётся в `onToggle` / `onRemove` |
|
|
79
|
+
| `num` | `number` | `undefined` | Номер в кружке слева от заголовка. Рендерится только вместе с `status` |
|
|
80
|
+
| `status` | `EStepStatus` | `undefined` | Статус шага (цвет кружка). Без него кружок не рендерится, даже если задан `num` |
|
|
81
|
+
| `stepHint` | `string` | `undefined` | Подсказка (`Tooltip` от `Step`) по наведению на кружок. Игнорируется при `disabled` |
|
|
82
|
+
| `opened` | `boolean` | `undefined` | Раскрыт ли элемент. Передан — элемент управляемый, не передан — раскрывается сам |
|
|
83
|
+
| `disabled` | `boolean` | `false` | Блокирует раскрытие и удаление, гасит подсказку шага, оставляет содержимое свёрнутым |
|
|
84
|
+
| `onToggle` | `(newOpened: boolean, id: string) => void` | `undefined` | Клик по заголовку. Первым аргументом — состояние, в которое элемент переходит |
|
|
85
|
+
| `onRemove` | `(id: string) => void` | `undefined` | Клик по кнопке удаления. Сама кнопка рендерится, **только** если колбэк передан |
|
|
86
|
+
| `transitionProps` | `IExpandAnimationProps["transitionProps"]` | `undefined` | Props `Transition` (react-transition-group), пробрасываются в `ExpandAnimation` |
|
|
112
87
|
|
|
113
|
-
|
|
88
|
+
Расширяет `Omit<React.HTMLAttributes<HTMLLIElement>, "title">` — `title` переопределён на
|
|
89
|
+
`ReactNode`, поэтому HTML-атрибут `title` на `<li>` через него не задать.
|
|
114
90
|
|
|
115
|
-
|
|
116
|
-
SIZE_TO_TITLE_SIZE_MAP = { SM: H3, MD: H3, LG: H2 } // Title внутри заголовка
|
|
117
|
-
```
|
|
91
|
+
### Управляемый и неуправляемый режим
|
|
118
92
|
|
|
119
|
-
|
|
93
|
+
Один и тот же prop `opened` переключает режим:
|
|
120
94
|
|
|
121
|
-
|
|
95
|
+
- **`opened` не передан** — элемент хранит раскрытие сам. Клик по заголовку меняет его и вызывает
|
|
96
|
+
`onToggle`.
|
|
97
|
+
- **`opened` передан** — раскрытие задаёт потребитель. Клик по заголовку **только** вызывает
|
|
98
|
+
`onToggle(!текущее, id)`; собственное состояние элемент не меняет и ждёт нового `opened`. Если
|
|
99
|
+
потребитель не обновит prop, элемент не раскроется.
|
|
122
100
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
101
|
+
Синхронизация `opened` → внутреннее состояние идёт **в фазе рендера** (паттерн React «adjusting
|
|
102
|
+
state when a prop changes»), а не в `useEffect`, поэтому промежуточный кадр со старым состоянием
|
|
103
|
+
не отрисовывается. Если `opened` из заданного становится `undefined`, элемент сохраняет последнее
|
|
104
|
+
синхронизированное значение и дальше управляет им сам.
|
|
126
105
|
|
|
127
|
-
|
|
128
|
-
|
|
106
|
+
«Открыт ровно один элемент» встроенным поведением **не является**: аккордеон не знает о состоянии
|
|
107
|
+
соседей. Потребитель держит id открытого элемента у себя и раздаёт `opened={id === openId}` —
|
|
108
|
+
см. story `OnlyOneOpenAtATime`.
|
|
129
109
|
|
|
130
|
-
###
|
|
110
|
+
### Удаление
|
|
131
111
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
```
|
|
112
|
+
`onRemove` только уведомляет — элемент себя не удаляет. Убрать его из списка должен потребитель
|
|
113
|
+
(см. story `Removable`).
|
|
135
114
|
|
|
136
|
-
|
|
137
|
-
правила из `@sberbusiness/icons-next/styles/icons.css`). Когда открыт —
|
|
138
|
-
класс не ставится, иконка статична.
|
|
115
|
+
### Размеры
|
|
139
116
|
|
|
140
|
-
|
|
117
|
+
| `size` | Паддинги заголовка / тела | `border-radius` | Отступ между элементами | Размер `Title` |
|
|
118
|
+
|---|---|---|---|---|
|
|
119
|
+
| `SM` | 16px | 16px | 16px | `H3` |
|
|
120
|
+
| `MD` | 24px | 24px | 16px | `H3` |
|
|
121
|
+
| `LG` | 32px | 32px | 24px | `H2` |
|
|
141
122
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
- `aria-expanded`, `aria-controls` (кнопка → контент), `role="region"`,
|
|
145
|
-
`aria-labelledby` (контент → кнопка) проставляются.
|
|
146
|
-
- Контент рендерится внутри `ExpandAnimation` с этими ARIA-атрибутами;
|
|
147
|
-
даже в свёрнутом состоянии `ExpandAnimation` может оставлять элемент в DOM
|
|
148
|
-
(зависит от реализации) — не полагайся на "нет DOM = нет фокусируемых".
|
|
123
|
+
В раскрытом состоянии нижний паддинг заголовка уменьшается (12 / 16 / 24px), чтобы заголовок и
|
|
124
|
+
тело не расходились.
|
|
149
125
|
|
|
150
|
-
|
|
126
|
+
**Адаптив** (`max-width: @screen-sm-max`) устроен в двух местах и они должны сходиться:
|
|
127
|
+
- LESS схлопывает `MD` до метрик `SM`, а `LG` — до метрик `MD`;
|
|
128
|
+
- `useMobileView()` в TSX принудительно ставит `Title` размер `H3` при любом `size`.
|
|
151
129
|
|
|
152
|
-
|
|
153
|
-
{onRemove && (
|
|
154
|
-
<span className={styles.remove}>
|
|
155
|
-
<ButtonIcon onClick={handleRemoveClick} disabled={disabled} title="Удалить">
|
|
156
|
-
<CrossStrokeSrvIcon24 paletteIndex={5} />
|
|
157
|
-
</ButtonIcon>
|
|
158
|
-
</span>
|
|
159
|
-
)}
|
|
160
|
-
```
|
|
130
|
+
---
|
|
161
131
|
|
|
162
|
-
|
|
163
|
-
мультиязычности (`aria-label`/`title` не должны быть на конкретном языке).
|
|
164
|
-
**Technical debt.** Кандидат на рефакторинг: вынести в prop `removeTitle`
|
|
165
|
-
или полностью убрать, оставив потребителю.
|
|
132
|
+
## Дизайн-токены
|
|
166
133
|
|
|
167
|
-
|
|
134
|
+
Переопределяются через `ThemeProvider` (prop `tokens`) — см. `ThemeProvider-ai.md` →
|
|
135
|
+
«Как переопределять токены». Значения по умолчанию —
|
|
136
|
+
`src/components/DesignTokens/components/IslandAccordion.ts`.
|
|
168
137
|
|
|
169
|
-
```
|
|
170
|
-
|
|
138
|
+
```text
|
|
139
|
+
IslandAccordion.Type1_Header_Background_Hover
|
|
140
|
+
IslandAccordion.Type2_Header_Background_Hover
|
|
141
|
+
IslandAccordion.Type3_Header_Background_Hover
|
|
142
|
+
IslandAccordion.Shadow_Focus
|
|
171
143
|
```
|
|
172
144
|
|
|
173
|
-
|
|
174
|
-
|
|
145
|
+
Hover-фон заголовка применяется только к **свёрнутому** элементу (`&.type1:not(.opened) .header:hover`).
|
|
146
|
+
`Shadow_Focus` — внутренняя рамка на `.header:focus-visible`. Фон и тень самой карточки берутся из
|
|
147
|
+
токенов `Island` (`Island.Type1_Background` и т.д.), собственных у аккордеона нет. Скругления и
|
|
148
|
+
отступы токенами не описаны — они заданы литералами в `styles/IslandAccordion.module.less`.
|
|
175
149
|
|
|
176
150
|
---
|
|
177
151
|
|
|
178
152
|
## Инварианты
|
|
179
153
|
|
|
180
|
-
- **`
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
-
|
|
184
|
-
|
|
185
|
-
- **`
|
|
186
|
-
|
|
187
|
-
-
|
|
154
|
+
- **`forwardRef`** у `IslandAccordion` (→ `<ul>`) и `IslandAccordionItem` (→ `<li>`) — не убирать.
|
|
155
|
+
У `IslandAccordionContent` и `IslandAccordionFooter` его нет: они объявлены как `React.FC`.
|
|
156
|
+
Добавление `forwardRef` этим двум — изменение публичного контракта.
|
|
157
|
+
- **Корневые элементы `<ul>` и `<li>`** — на них держатся роли `list` / `listitem` и селекторы
|
|
158
|
+
отступов между элементами (`.item + .item.md`). Смена тега ломает и семантику, и вёрстку.
|
|
159
|
+
- **`id` у `Item` обязателен и уникален в пределах страницы** — он идёт прямо в DOM-атрибут `id`
|
|
160
|
+
корневого `<li>`, а не только в колбэки. Дубли дадут невалидный DOM.
|
|
161
|
+
- **Публичный API:** `IslandAccordion`, `IslandAccordionItem`, `IslandAccordionContent`,
|
|
162
|
+
`IslandAccordionFooter` и их интерфейсы props экспортируются из
|
|
163
|
+
`src/components/IslandAccordion/index.ts`. `IslandAccordionContext`,
|
|
164
|
+
`IIslandAccordionContext` и `initialIslandAccordionContext` в barrel **не** реэкспортируются —
|
|
165
|
+
это внутренний слой, менять его можно свободно.
|
|
166
|
+
- **`margin-top: 0 !important` у `.footer`** гасит правило отступов `Island`
|
|
167
|
+
(`.island.{size} .islandBody + .islandFooter`), у которого специфичность в четыре класса.
|
|
168
|
+
Убрать `!important` без переписывания селектора нельзя — подвал разъедется. Подробности —
|
|
169
|
+
в комментарии в LESS и в `Island-ai.md` → «Ограничения».
|
|
170
|
+
- **`.body` / `.footer` скрыты через `visibility: hidden`**, пока у элемента нет класса `.opened` —
|
|
171
|
+
«чтобы focus не попадал во внутренние элементы в закрытом состоянии» (комментарий в LESS).
|
|
172
|
+
`ExpandAnimation` независимо ставит `visibility: hidden` инлайном в фазе `exited`; классы
|
|
173
|
+
аккордеона переключаются синхронно с состоянием, инлайн-стиль — по завершении анимации.
|
|
174
|
+
Удаление любого из двух механизмов меняет момент, начиная с которого содержимое перестаёт
|
|
175
|
+
ловить фокус.
|
|
176
|
+
- **Глобальный класс `hoverable`** на кнопке-заголовке приходит из `@sberbusiness/icons-next` и
|
|
177
|
+
задаёт цвет каретки при наведении. Он не из `styles` — при чистке «мёртвых» классов не удалять.
|
|
178
|
+
- **Кружок шага рендерится только при `status` И `num`.** Условие намеренно записано тернарником
|
|
179
|
+
с явными проверками (`num !== undefined && num !== 0`), а не через `&&`: выражение `status && num`
|
|
180
|
+
при `num === 0` вернуло бы `0`, и React отрисовал бы текст «0». Намеренна здесь только форма записи.
|
|
181
|
+
Сам пропуск кружка при `num === 0` — **унаследованная особенность, а не желаемый контракт**:
|
|
182
|
+
ноль выглядит валидным номером шага, но исторически не рендерится. Поведение зафиксировано тестом
|
|
183
|
+
как есть; менять его — отдельная задача с записью в release notes, потому что это изменение
|
|
184
|
+
наблюдаемого поведения.
|
|
188
185
|
|
|
189
186
|
---
|
|
190
187
|
|
|
191
188
|
## Accessibility
|
|
192
189
|
|
|
193
|
-
-
|
|
194
|
-
|
|
195
|
-
- `
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
-
|
|
190
|
+
- Заголовок — нативный `<button type="button">` внутри `Island.Header`. Клавиатура работает
|
|
191
|
+
штатно (`Enter` / `Space`), собственных обработчиков клавиш компонент не ставит.
|
|
192
|
+
- Связка заголовка и содержимого: у кнопки `id` и `aria-controls`, у раскрывающейся области
|
|
193
|
+
`role="region"` и `aria-labelledby`. Оба id генерируются автоматически
|
|
194
|
+
(`IslandAccordionItem-<n>-header` / `-body`) — потребителю их задавать не нужно и нельзя.
|
|
195
|
+
- `aria-expanded` на кнопке отражает **запрошенное** состояние. При `disabled` содержимое остаётся
|
|
196
|
+
свёрнутым (`expanded={isOpen && !disabled}`), а `aria-expanded` может показывать `true`, если
|
|
197
|
+
передан `opened` — это известное расхождение.
|
|
198
|
+
- Каретка помечена `aria-hidden="true"` — она декоративная.
|
|
199
|
+
- Навигации стрелками между элементами нет (в WAI-ARIA Accordion Pattern она опциональна). Между
|
|
200
|
+
заголовками ходят `Tab`.
|
|
201
|
+
- Список не получает `aria-label` автоматически. Если на странице несколько списков, передай его
|
|
202
|
+
сам в `IslandAccordion`.
|
|
203
|
+
- Фокус при раскрытии никуда не переносится — он остаётся на заголовке.
|
|
204
|
+
- **Кнопка удаления имеет захардкоженный `title="Удалить"`** — отступление от правила
|
|
205
|
+
мультиязычности `docs/ai/codestyle.md`. Задать его снаружи сейчас нельзя; починка требует нового
|
|
206
|
+
prop, то есть изменения публичного API.
|
|
199
207
|
|
|
200
208
|
---
|
|
201
209
|
|
|
202
|
-
##
|
|
210
|
+
## Связанные компоненты
|
|
203
211
|
|
|
204
|
-
|
|
205
|
-
Chevron-цвета — из `@sberbusiness/icons-next` (palette 5).
|
|
212
|
+
### Части IslandAccordion (своего AI.md не имеют)
|
|
206
213
|
|
|
207
|
-
|
|
214
|
+
- `IslandAccordionItem` (он же `IslandAccordion.Item`) — раскрывающийся элемент. Вся логика
|
|
215
|
+
состояния, статуса и удаления живёт здесь.
|
|
216
|
+
- `IslandAccordionContent` (он же `IslandAccordion.Item.Content`) — обёртка над `Island.Body`,
|
|
217
|
+
добавляющая класс с отступами аккордеона. Собственных props, кроме `children`, нет:
|
|
218
|
+
`className` и `...rest` она не принимает.
|
|
219
|
+
- `IslandAccordionFooter` (он же `IslandAccordion.Item.Footer`) — обёртка над `Island.Footer`.
|
|
220
|
+
`IIslandAccordionFooterProps extends IIslandFooterProps`, `className` мерджится с базовым.
|
|
208
221
|
|
|
209
|
-
|
|
222
|
+
### Альтернативы (в `related`)
|
|
223
|
+
|
|
224
|
+
- `Island` — та же карточка без раскрытия. `IslandAccordion` строится на нём и опирается на его
|
|
225
|
+
вёрстку: гасит отступ `Island.Footer` через `!important` и обнуляет паддинги `Island` в свою
|
|
226
|
+
пользу, поэтому правки селекторов отступов `Island` ломают аккордеон молча.
|
|
227
|
+
- `IslandWidget` — виджет на `Island`, который сворачивается в адаптиве, а не по клику. Свои
|
|
228
|
+
Header / Body / Footer, частями `Island` не пользуется.
|
|
229
|
+
|
|
230
|
+
### Контракты по рендеру (в `related`)
|
|
210
231
|
|
|
211
|
-
- `
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
- `
|
|
215
|
-
|
|
232
|
+
- `ExpandAnimation` — анимация раскрытия содержимого. Получает `role="region"`, `aria-labelledby`
|
|
233
|
+
и `transitionProps` от элемента. Именно он держит содержимое в DOM в свёрнутом состоянии
|
|
234
|
+
(`height: 0` + `visibility: hidden`), а не размонтирует его.
|
|
235
|
+
- `Step` — кружок с номером и статусом слева от заголовка. `position` всегда `XFirst`,
|
|
236
|
+
`size` берётся из контекста аккордеона, `stepHint` уходит в `children` и превращается в `Tooltip`.
|
|
237
|
+
- `ButtonIcon` — кнопка удаления справа от карточки (абсолютно спозиционирована **за** её
|
|
238
|
+
границами: `right: -36px` / `-40px`, поэтому у аккордеона с `onRemove` нужен внешний отступ справа).
|
|
239
|
+
|
|
240
|
+
### Другие направления из «Не используй когда» (в `related` не входят)
|
|
241
|
+
|
|
242
|
+
- `Tabs` — если виден ровно один раздел, а заголовки стоят в ряд.
|
|
243
|
+
- `CollapsibleTree`, `CollapsibleTreeExtended` — если нужна древовидная структура.
|
|
216
244
|
|
|
217
245
|
---
|
|
218
246
|
|
|
219
247
|
## Stories
|
|
220
248
|
|
|
221
|
-
`stories/IslandAccordion/IslandAccordion.stories.tsx`
|
|
222
|
-
|
|
249
|
+
Основные истории: `stories/IslandAccordion/IslandAccordion.stories.tsx`
|
|
250
|
+
Файлы примеров: `stories/IslandAccordion/examples/`
|
|
251
|
+
|
|
252
|
+
| Story | Example file | Что демонстрирует |
|
|
253
|
+
|---|---|---|
|
|
254
|
+
| `Playground` | — | Интерактивный контроль `size`, `type`, `status`, `removable`, `disabled`, `title` |
|
|
255
|
+
| `Default` | `DefaultExample.tsx` | Минимальный элемент: заголовок, `Content`, `Footer` с кнопками |
|
|
256
|
+
| `Sizes` | `SizesExample.tsx` | Размеры SM / MD / LG: паддинги, скругление, размер заголовка |
|
|
257
|
+
| `Disabled` | `DisabledExample.tsx` | `disabled`: заголовок не кликается, содержимое свёрнуто |
|
|
258
|
+
| `Removable` | `RemovableExample.tsx` | `onRemove`: кнопка удаления и удаление элемента силами потребителя |
|
|
259
|
+
| `WithStatus` | `WithStatusExample.tsx` | Все значения `EStepStatus` в кружке шага |
|
|
260
|
+
| `WithStepHint` | `WithStepHintExample.tsx` | `stepHint`: подсказка `Tooltip` по наведению на кружок |
|
|
261
|
+
| `OnlyOneOpenAtATime` | `OnlyOneOpenAtATimeExample.tsx` | Управляемый режим: одновременно раскрыт не больше одного элемента |
|
|
262
|
+
| `VisualTests` | — | Скриншот-регрессия: раскрытый и свёрнутый элементы рядом |
|
|
263
|
+
|
|
264
|
+
Скриншот-регрессия снимается со stories `Default`, `Sizes`, `Disabled`, `Removable`,
|
|
265
|
+
`WithStatus`, `VisualTests`. `Playground`, `WithStepHint` и `OnlyOneOpenAtATime` помечены
|
|
266
|
+
`testRunner: { skip: true }` — они интерактивные и дают нестабильный кадр.
|
|
223
267
|
|
|
224
268
|
---
|
|
225
269
|
|
|
@@ -227,4 +271,251 @@ Chevron-цвета — из `@sberbusiness/icons-next` (palette 5).
|
|
|
227
271
|
|
|
228
272
|
| Дата | Изменение |
|
|
229
273
|
|---|---|
|
|
230
|
-
| 2026-
|
|
274
|
+
| 2026-09-09 | Создан документ. AI-рефакторинг: JSDoc на props, компонентах и контексте; синхронизация `opened` перенесена из `useEffect` в фазу рендера (промежуточный кадр со старым состоянием больше не отрисовывается); автогенерируемые id заголовка и содержимого получили вид `IslandAccordionItem-<n>-header` / `-body`; при `num={0}` со статусом больше не отрисовывается текст «0»; добавлены unit-тесты на `IslandAccordionItem`, контекст, части и публичный контракт `IslandAccordion`. Публичный API не изменён. |
|
|
275
|
+
|
|
276
|
+
---
|
|
277
|
+
|
|
278
|
+
## Примеры использования
|
|
279
|
+
|
|
280
|
+
### Default
|
|
281
|
+
|
|
282
|
+
`stories/IslandAccordion/examples/DefaultExample.tsx`
|
|
283
|
+
|
|
284
|
+
```tsx
|
|
285
|
+
import React from "react";
|
|
286
|
+
import { IslandAccordion, Button, EButtonTheme, EComponentSize } from "@sberbusiness/triplex-next";
|
|
287
|
+
|
|
288
|
+
export const DefaultExample = () => (
|
|
289
|
+
<IslandAccordion>
|
|
290
|
+
<IslandAccordion.Item id="island-accordion-default" num={1} title="Title">
|
|
291
|
+
<IslandAccordion.Item.Content>Content</IslandAccordion.Item.Content>
|
|
292
|
+
<IslandAccordion.Item.Footer>
|
|
293
|
+
<Button theme={EButtonTheme.LINK} size={EComponentSize.MD}>
|
|
294
|
+
Button link text
|
|
295
|
+
</Button>
|
|
296
|
+
<Button theme={EButtonTheme.SECONDARY} size={EComponentSize.MD}>
|
|
297
|
+
Button text
|
|
298
|
+
</Button>
|
|
299
|
+
<Button theme={EButtonTheme.GENERAL} size={EComponentSize.MD}>
|
|
300
|
+
Button text
|
|
301
|
+
</Button>
|
|
302
|
+
</IslandAccordion.Item.Footer>
|
|
303
|
+
</IslandAccordion.Item>
|
|
304
|
+
</IslandAccordion>
|
|
305
|
+
);
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### Sizes
|
|
309
|
+
|
|
310
|
+
`stories/IslandAccordion/examples/SizesExample.tsx`
|
|
311
|
+
|
|
312
|
+
```tsx
|
|
313
|
+
import React from "react";
|
|
314
|
+
import { IslandAccordion, Button, EButtonTheme, EComponentSize } from "@sberbusiness/triplex-next";
|
|
315
|
+
|
|
316
|
+
const SIZES = Object.values(EComponentSize);
|
|
317
|
+
|
|
318
|
+
export const SizesExample = () => (
|
|
319
|
+
<div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
|
|
320
|
+
{SIZES.map((size, index) => (
|
|
321
|
+
<div key={size}>
|
|
322
|
+
<div style={{ marginBottom: "8px", fontSize: "16px", fontWeight: "700" }}>{size.toUpperCase()}</div>
|
|
323
|
+
<IslandAccordion size={size}>
|
|
324
|
+
<IslandAccordion.Item id={`island-accordion-sizes-${size}`} num={index + 1} title="Title">
|
|
325
|
+
<IslandAccordion.Item.Content>Content</IslandAccordion.Item.Content>
|
|
326
|
+
<IslandAccordion.Item.Footer>
|
|
327
|
+
<Button theme={EButtonTheme.LINK} size={size}>
|
|
328
|
+
Button link text
|
|
329
|
+
</Button>
|
|
330
|
+
<Button theme={EButtonTheme.SECONDARY} size={size}>
|
|
331
|
+
Button text
|
|
332
|
+
</Button>
|
|
333
|
+
<Button theme={EButtonTheme.GENERAL} size={size}>
|
|
334
|
+
Button text
|
|
335
|
+
</Button>
|
|
336
|
+
</IslandAccordion.Item.Footer>
|
|
337
|
+
</IslandAccordion.Item>
|
|
338
|
+
</IslandAccordion>
|
|
339
|
+
</div>
|
|
340
|
+
))}
|
|
341
|
+
</div>
|
|
342
|
+
);
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
### Disabled
|
|
346
|
+
|
|
347
|
+
`stories/IslandAccordion/examples/DisabledExample.tsx`
|
|
348
|
+
|
|
349
|
+
```tsx
|
|
350
|
+
import React from "react";
|
|
351
|
+
import { IslandAccordion, Button, EButtonTheme, EComponentSize } from "@sberbusiness/triplex-next";
|
|
352
|
+
|
|
353
|
+
export const DisabledExample = () => (
|
|
354
|
+
<IslandAccordion>
|
|
355
|
+
<IslandAccordion.Item id="island-accordion-disabled" num={1} title="Title" disabled>
|
|
356
|
+
<IslandAccordion.Item.Content>Контент аккордеона</IslandAccordion.Item.Content>
|
|
357
|
+
<IslandAccordion.Item.Footer>
|
|
358
|
+
<Button theme={EButtonTheme.GENERAL} size={EComponentSize.MD}>
|
|
359
|
+
Button Name
|
|
360
|
+
</Button>
|
|
361
|
+
</IslandAccordion.Item.Footer>
|
|
362
|
+
</IslandAccordion.Item>
|
|
363
|
+
</IslandAccordion>
|
|
364
|
+
);
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
### Removable
|
|
368
|
+
|
|
369
|
+
`stories/IslandAccordion/examples/RemovableExample.tsx`
|
|
370
|
+
|
|
371
|
+
```tsx
|
|
372
|
+
import React from "react";
|
|
373
|
+
import { IslandAccordion, Button, EButtonTheme, EComponentSize } from "@sberbusiness/triplex-next";
|
|
374
|
+
|
|
375
|
+
export const RemovableExample = () => {
|
|
376
|
+
const handleRemove = (id: string) => document.getElementById(id)?.remove();
|
|
377
|
+
|
|
378
|
+
return (
|
|
379
|
+
<IslandAccordion>
|
|
380
|
+
<IslandAccordion.Item id="island-accordion-removable" num={1} title="Title" onRemove={handleRemove}>
|
|
381
|
+
<IslandAccordion.Item.Content>Контент аккордеона</IslandAccordion.Item.Content>
|
|
382
|
+
<IslandAccordion.Item.Footer>
|
|
383
|
+
<Button theme={EButtonTheme.LINK} size={EComponentSize.MD}>
|
|
384
|
+
Button link text
|
|
385
|
+
</Button>
|
|
386
|
+
<Button theme={EButtonTheme.SECONDARY} size={EComponentSize.MD}>
|
|
387
|
+
Button text
|
|
388
|
+
</Button>
|
|
389
|
+
<Button theme={EButtonTheme.GENERAL} size={EComponentSize.MD}>
|
|
390
|
+
Button text
|
|
391
|
+
</Button>
|
|
392
|
+
</IslandAccordion.Item.Footer>
|
|
393
|
+
</IslandAccordion.Item>
|
|
394
|
+
</IslandAccordion>
|
|
395
|
+
);
|
|
396
|
+
};
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
### WithStatus
|
|
400
|
+
|
|
401
|
+
`stories/IslandAccordion/examples/WithStatusExample.tsx`
|
|
402
|
+
|
|
403
|
+
```tsx
|
|
404
|
+
import React from "react";
|
|
405
|
+
import { IslandAccordion, Button, EButtonTheme, EComponentSize, EStepStatus } from "@sberbusiness/triplex-next";
|
|
406
|
+
|
|
407
|
+
const ITEMS = [
|
|
408
|
+
{ id: "accordion-status-0", status: EStepStatus.DEFAULT },
|
|
409
|
+
{ id: "accordion-status-1", status: EStepStatus.DONE },
|
|
410
|
+
{ id: "accordion-status-2", status: EStepStatus.ACTIVE },
|
|
411
|
+
{ id: "accordion-status-3", status: EStepStatus.ERROR },
|
|
412
|
+
{ id: "accordion-status-4", status: EStepStatus.DISABLED },
|
|
413
|
+
{ id: "accordion-status-5", status: EStepStatus.WARNING },
|
|
414
|
+
];
|
|
415
|
+
|
|
416
|
+
export const WithStatusExample = () => (
|
|
417
|
+
<IslandAccordion>
|
|
418
|
+
{ITEMS.map(({ id, status }, index) => (
|
|
419
|
+
<IslandAccordion.Item key={id} id={id} num={index + 1} status={status} title="Title">
|
|
420
|
+
<IslandAccordion.Item.Content>Content</IslandAccordion.Item.Content>
|
|
421
|
+
<IslandAccordion.Item.Footer>
|
|
422
|
+
<Button theme={EButtonTheme.LINK} size={EComponentSize.MD}>
|
|
423
|
+
Button link text
|
|
424
|
+
</Button>
|
|
425
|
+
<Button theme={EButtonTheme.SECONDARY} size={EComponentSize.MD}>
|
|
426
|
+
Button text
|
|
427
|
+
</Button>
|
|
428
|
+
<Button theme={EButtonTheme.GENERAL} size={EComponentSize.MD}>
|
|
429
|
+
Button text
|
|
430
|
+
</Button>
|
|
431
|
+
</IslandAccordion.Item.Footer>
|
|
432
|
+
</IslandAccordion.Item>
|
|
433
|
+
))}
|
|
434
|
+
</IslandAccordion>
|
|
435
|
+
);
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
### WithStepHint
|
|
439
|
+
|
|
440
|
+
`stories/IslandAccordion/examples/WithStepHintExample.tsx`
|
|
441
|
+
|
|
442
|
+
```tsx
|
|
443
|
+
import React from "react";
|
|
444
|
+
import { IslandAccordion, Button, EButtonTheme, EComponentSize, EStepStatus } from "@sberbusiness/triplex-next";
|
|
445
|
+
|
|
446
|
+
export const WithStepHintExample = () => (
|
|
447
|
+
<IslandAccordion>
|
|
448
|
+
<IslandAccordion.Item
|
|
449
|
+
id="island-accordion-step-hint"
|
|
450
|
+
num={1}
|
|
451
|
+
status={EStepStatus.DONE}
|
|
452
|
+
title="Title"
|
|
453
|
+
stepHint="Текст подсказки."
|
|
454
|
+
>
|
|
455
|
+
<IslandAccordion.Item.Content>Content</IslandAccordion.Item.Content>
|
|
456
|
+
<IslandAccordion.Item.Footer>
|
|
457
|
+
<Button theme={EButtonTheme.LINK} size={EComponentSize.MD}>
|
|
458
|
+
Button link text
|
|
459
|
+
</Button>
|
|
460
|
+
<Button theme={EButtonTheme.SECONDARY} size={EComponentSize.MD}>
|
|
461
|
+
Button text
|
|
462
|
+
</Button>
|
|
463
|
+
<Button theme={EButtonTheme.GENERAL} size={EComponentSize.MD}>
|
|
464
|
+
Button text
|
|
465
|
+
</Button>
|
|
466
|
+
</IslandAccordion.Item.Footer>
|
|
467
|
+
</IslandAccordion.Item>
|
|
468
|
+
</IslandAccordion>
|
|
469
|
+
);
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
### OnlyOneOpenAtATime
|
|
473
|
+
|
|
474
|
+
`stories/IslandAccordion/examples/OnlyOneOpenAtATimeExample.tsx`
|
|
475
|
+
|
|
476
|
+
```tsx
|
|
477
|
+
import React, { useState } from "react";
|
|
478
|
+
import { IslandAccordion, Button, EButtonTheme, EComponentSize, EStepStatus } from "@sberbusiness/triplex-next";
|
|
479
|
+
|
|
480
|
+
const ITEMS = [
|
|
481
|
+
{ id: "accordion-single-0", status: EStepStatus.DEFAULT },
|
|
482
|
+
{ id: "accordion-single-1", status: EStepStatus.DONE },
|
|
483
|
+
{ id: "accordion-single-2", status: EStepStatus.ACTIVE },
|
|
484
|
+
];
|
|
485
|
+
|
|
486
|
+
export const OnlyOneOpenAtATimeExample = () => {
|
|
487
|
+
const [openItemId, setOpenItemId] = useState<string>();
|
|
488
|
+
|
|
489
|
+
const handleToggle = (open: boolean, id: string) => setOpenItemId(open ? id : undefined);
|
|
490
|
+
|
|
491
|
+
return (
|
|
492
|
+
<IslandAccordion>
|
|
493
|
+
{ITEMS.map(({ id, status }, index) => (
|
|
494
|
+
<IslandAccordion.Item
|
|
495
|
+
key={id}
|
|
496
|
+
id={id}
|
|
497
|
+
num={index + 1}
|
|
498
|
+
status={status}
|
|
499
|
+
title="Title"
|
|
500
|
+
opened={id === openItemId}
|
|
501
|
+
onToggle={handleToggle}
|
|
502
|
+
>
|
|
503
|
+
<IslandAccordion.Item.Content>Content</IslandAccordion.Item.Content>
|
|
504
|
+
<IslandAccordion.Item.Footer>
|
|
505
|
+
<Button theme={EButtonTheme.LINK} size={EComponentSize.MD}>
|
|
506
|
+
Button link text
|
|
507
|
+
</Button>
|
|
508
|
+
<Button theme={EButtonTheme.SECONDARY} size={EComponentSize.MD}>
|
|
509
|
+
Button text
|
|
510
|
+
</Button>
|
|
511
|
+
<Button theme={EButtonTheme.GENERAL} size={EComponentSize.MD}>
|
|
512
|
+
Button text
|
|
513
|
+
</Button>
|
|
514
|
+
</IslandAccordion.Item.Footer>
|
|
515
|
+
</IslandAccordion.Item>
|
|
516
|
+
))}
|
|
517
|
+
</IslandAccordion>
|
|
518
|
+
);
|
|
519
|
+
};
|
|
520
|
+
```
|
|
521
|
+
|