@sberbusiness/triplex-next-mcp-server 1.40.0-beta → 1.41.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.
@@ -1,9 +1,7 @@
1
1
  ---
2
2
  component: IconWrapper
3
3
  category: Icons
4
- figma-node: ""
5
- figma-file: ""
6
- related: [ButtonIcon, Button, Link]
4
+ related: [ButtonIcon, Button, Link, Chip, ListItemControlsButton]
7
5
  tokens: []
8
6
  stories: stories/Icons/IconWrapper/IconWrapper.stories.tsx
9
7
  version: "1.0"
@@ -13,128 +11,129 @@ version: "1.0"
13
11
 
14
12
  ## Назначение
15
13
 
16
- Минимальный `<span>`-обёртка для иконок из `@sberbusiness/icons-next`,
17
- которая проставляет **глобальный CSS-класс `hoverable`** + опционально `active`/`disabled`.
18
- Эти имена используются CSS-правилами из `icons-next/styles/icons.css` для
19
- смены `fill` у палитры иконок при hover/active/disabled.
14
+ Обёртка-`<span>` для иконок из `@sberbusiness/icons-next`. Сама ничего не рисует: выставляет
15
+ на корневом элементе глобальные классы `hoverable` / `active` / `disabled`, по которым стили
16
+ пакета иконок подбирают цвет вложенной иконки в обычном, наведённом, активном и отключённом
17
+ состоянии.
20
18
 
21
- Без обёртки иконка имеет фиксированные цвета палитры. С обёрткой — реагирует
22
- на состояния родителя.
19
+ Используй когда: иконка должна менять цвет вместе с состоянием интерактивного элемента
20
+ (кнопка, ссылка, чип, строка списка) — оборачивай ею иконку и прокидывай `active` / `disabled`
21
+ из состояния родителя.
23
22
 
24
- Используй когда: нужно сделать кликабельную/интерактивную иконку вне
25
- кнопки (например, в строке таблицы, в свободной вёрстке), либо когда
26
- иконка внутри нестандартного контейнера должна менять цвет при hover/active.
27
- Не используй когда: иконка внутри `ButtonIcon`/`Button` — там `hoverable`
28
- уже навешен.
23
+ Не используй когда:
24
+ - Нужен интерактивный элемент — IconWrapper не кнопка: это `<span>` без роли, фокуса и
25
+ клавиатурной обработки, и сам он ничего из этого не реализует, даже если прокинуть
26
+ обработчики через spread. Возьми `ButtonIcon` (он сам оборачивает `<button>` в IconWrapper).
27
+ - Иконка статична и её цвет задаётся напрямую через `paletteIndex` иконки или CSS родителя —
28
+ обёртка избыточна.
29
+ - Нужно менять размер иконки — IconWrapper не влияет на размер, его задаёт сама иконка
30
+ (`...Icon16` / `...Icon20` / `...Icon24`).
29
31
 
30
32
  ---
31
33
 
32
- ## Файловая структура
34
+ ## Варианты и props
33
35
 
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
- ```
36
+ ### Обязательные props
41
37
 
42
- ---
43
-
44
- ## Варианты и props
38
+ | Prop | Тип | Описание |
39
+ |---|---|---|
40
+ | `children` | `React.ReactNode` | Иконка (или произвольный контент), которую оборачивает компонент |
45
41
 
46
- ### `IIconWrapperProps extends HTMLAttributes<HTMLSpanElement>`
42
+ ### Опциональные props
47
43
 
48
- | Prop | Тип | Default | Описание |
44
+ | Prop | Тип | По умолчанию | Описание |
49
45
  |---|---|---|---|
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` | — | — | Стандартные. |
46
+ | `active` | `boolean` | `false` | Ставит глобальный класс `active` — цвет иконки для нажатого/выбранного состояния |
47
+ | `disabled` | `boolean` | `false` | Ставит глобальный класс `disabled` — приглушённый цвет иконки. Это только визуальное состояние, взаимодействие не блокируется |
48
+ | `disableInteraction` | `boolean` | `false` | `pointer-events: none` на обёртке. Нужен, чтобы иконка выглядела disabled и не реагировала на hover, когда клика по ней не предполагается |
49
+ | `displayContents` | `boolean` | `false` | `display: contents` — обёртка исчезает из layout, потомок наследует место `<span>` в сетке/флексе родителя |
50
+
51
+ Компонент расширяет `React.HTMLAttributes<HTMLSpanElement>`, поэтому принимает все стандартные
52
+ атрибуты `<span>` (`className`, `style`, `onClick`, `aria-*`, `data-*`) — они попадают на корневой
53
+ элемент.
54
+
55
+ ### Ограничения
56
+
57
+ - `disabled` и `disableInteraction` независимы: `disabled` красит, `disableInteraction` гасит
58
+ события мыши. Для «выглядит выключенным и не кликается» нужны оба — пример:
59
+ `stories/MultiselectField/examples/StatusesExample.tsx`. В `src/**` `disableInteraction`
60
+ не передаёт ни один компонент.
61
+ - `displayContents` меняет только участие обёртки в layout; классы состояний по-прежнему стоят
62
+ на `<span>`, и сам `<span>` остаётся в DOM — на это опираются тесты потребителей, которые
63
+ достают обёртку через `parentElement` (`Button/__tests__/ButtonIcon.test.tsx`,
64
+ `IconWrapper/__tests__/IconWrapper.test.tsx`).
65
+ - Класс `hoverable` выставляется всегда, включая `disabled`-состояние — так работает цветовая
66
+ логика `@sberbusiness/icons-next`.
56
67
 
57
68
  ---
58
69
 
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>`
70
+ ## Дизайн-токены
91
71
 
92
- `IIconWrapperProps` требует `children: React.ReactNode` как обязательный prop (в отличие от стандартного `HTMLAttributes`, где `children` опциональный).
72
+ Компонент не использует собственных CSS-переменных. Цвета иконок задают глобальные стили
73
+ `@sberbusiness/icons-next`, привязанные к классам `hoverable` / `active` / `disabled`. Собственный
74
+ CSS-модуль `styles/IconWrapper.module.less` содержит только два служебных класса:
75
+ `.disableInteraction` (`pointer-events: none`) и `.displayContents` (`display: contents`).
93
76
 
94
77
  ---
95
78
 
96
79
  ## Инварианты
97
80
 
98
- - **Строковые классы `"hoverable"` / `"active"` / `"disabled"`** — кросс-пакетный контракт с `@sberbusiness/icons-next`. Не менять и не оборачивать в CSS-Modules.
99
- - **`forwardRef`** — не убирать.
100
- - **`children` обязателен** по типу — тонкая обёртка без контента бессмысленна.
101
- - **`displayContents`** — используется там, где wrapper мешает layout'у родителя; если удаляешь, нужно проверить потребителей.
81
+ - **`forwardRef`** — обязателен, не убирать. `ref` пробрасывается на корневой `<span>`.
82
+ - **Корневой элемент — `<span>`.** Смена тега сломает потребителей, которые верстают IconWrapper
83
+ внутри инлайн-контекста и опираются на `parentElement`.
84
+ - **Глобальные классы `hoverable` / `active` / `disabled`** приходят не из CSS-модуля, а из
85
+ `@sberbusiness/icons-next`, поэтому пишутся строковыми литералами и не должны хешироваться или
86
+ переименовываться. На них опираются тесты других компонентов (например,
87
+ `ListItemControlsButton.test.tsx` ищет `.hoverable`).
88
+ - **Имена `IconWrapper`, `IIconWrapperProps`** и все props (`active`, `disabled`,
89
+ `disableInteraction`, `displayContents`) — часть публичного API, экспортируются из barrel
90
+ `src/components/IconWrapper/index.ts`.
91
+ - **`children` обязателен** по типу — обёртка без содержимого бессмысленна.
102
92
 
103
93
  ---
104
94
 
105
95
  ## Accessibility
106
96
 
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`.
97
+ - Компонент неинтерактивен и не добавляет ARIA-атрибутов: `<span>` без роли остаётся прозрачным
98
+ для assistive technologies, доступное имя даёт интерактивный родитель (`ButtonIcon`, `Link`,
99
+ `ListItemControlsButton`).
100
+ - `disabled` — чисто визуальный класс, он не выставляет `aria-disabled` и не блокирует события.
101
+ Реальную блокировку обеспечивает вложенный `<button disabled>` или `disableInteraction` у
102
+ потребителя.
103
+ - Если иконка декоративная и рядом есть текст — потребитель передаёт `aria-hidden` через spread.
104
+ Компонент не хардкодит текстовых строк (библиотека мультиязычная).
118
105
 
119
106
  ---
120
107
 
121
108
  ## Связанные компоненты
122
109
 
123
- - `ButtonIcon` / `Button` — уже добавляют `hoverable` вокруг иконки. Использовать IconWrapper внутри них **не нужно**.
124
- - `List` / `Table` — используют `IconWrapper` в служебных местах (сортировка, экспанд).
125
- - `IslandAccordion` — использует прямую строку `"hoverable"` для chevron-иконки (тот же паттерн, без компонента).
110
+ - `ButtonIcon` (`src/components/Button/ButtonIcon.tsx`) — оборачивает `<button>` в
111
+ `IconWrapper displayContents` и прокидывает в него `active` / `disabled`. Основной способ
112
+ сделать кликабельную иконку.
113
+ - `Link` (`src/components/Link/Link.tsx`) — оборачивает содержимое ссылки в
114
+ `IconWrapper displayContents`, чтобы иконка внутри ссылки красилась вместе с текстом.
115
+ - `Button` (`src/components/Button/Button.tsx`) — оборачивает содержимое кнопки в
116
+ `IconWrapper` с кастомным `className` и прокидывает `disabled` / `active`. Единственный
117
+ в `src/**` случай передачи собственного `className` в IconWrapper.
118
+ - `Chip`, `ChipSort`, `SegmentedControlSegment`, `StepperStep`, `ListItemControlsButton`,
119
+ `SelectExtendedFieldTarget` — используют IconWrapper для синхронизации цвета иконки
120
+ с состоянием элемента.
121
+ - `CollapsibleTreeNodeHeader` (`src/components/CollapsibleTree/components/`) — не использует
122
+ IconWrapper, но ставит те же глобальные классы напрямую на `<button>`; при изменении логики
123
+ классов держи оба места синхронными.
126
124
 
127
125
  ---
128
126
 
129
127
  ## Stories
130
128
 
131
- `stories/Icons/IconWrapper/IconWrapper.stories.tsx`
132
- Примеры: `stories/Icons/IconWrapper/examples/`
129
+ Основные истории: `stories/Icons/IconWrapper/IconWrapper.stories.tsx`
130
+ Файлы примеров: `stories/Icons/IconWrapper/examples/`
133
131
 
134
132
  | Story | Example file | Что демонстрирует |
135
133
  |---|---|---|
136
- | `Default` | `DefaultExample.tsx` | Базовое использование с иконкой из icons-next. |
137
- | `States` | `StatesExample.tsx` | `active` / `disabled` / `disableInteraction`. |
134
+ | `Playground` | — | Интерактивный контроль `active`, `disabled`, `disableInteraction`, `displayContents` |
135
+ | `Default` | `DefaultExample.tsx` | Минимальное использование: обёртка вокруг иконки `@sberbusiness/icons-next` |
136
+ | `States` | `StatesExample.tsx` | Состояния Default / Active / Disabled рядом |
138
137
 
139
138
  ---
140
139
 
@@ -142,4 +141,58 @@ className={clsx("hoverable", className, {
142
141
 
143
142
  | Дата | Изменение |
144
143
  |---|---|
145
- | 2026-04-18 | Создан документ |
144
+ | 2026-08-03 | Создан документ. AI-рефакторинг: JSDoc на `children`, комментарий о происхождении глобальных классов icons-next, убраны избыточные `!!` в `clsx`, `{...props}` перенесён перед управляемыми атрибутами, расширены unit-тесты (`displayContents`, дефолты, мердж `className`, spread rest-props, `onClick`). Публичный API не изменён. |
145
+
146
+ ---
147
+
148
+ ## Примеры использования
149
+
150
+ ### Default
151
+
152
+ `stories/Icons/IconWrapper/examples/DefaultExample.tsx`
153
+
154
+ ```tsx
155
+ import React from "react";
156
+ import { IconWrapper } from "@sberbusiness/triplex-next";
157
+ import { SettingsStrokeSrvIcon20 } from "@sberbusiness/icons-next";
158
+
159
+ export const DefaultExample = () => (
160
+ <IconWrapper>
161
+ <SettingsStrokeSrvIcon20 paletteIndex={5} />
162
+ </IconWrapper>
163
+ );
164
+ ```
165
+
166
+ ### States
167
+
168
+ `stories/Icons/IconWrapper/examples/StatesExample.tsx`
169
+
170
+ ```tsx
171
+ import React from "react";
172
+ import { IconWrapper } from "@sberbusiness/triplex-next";
173
+ import { SettingsStrokeSrvIcon20 } from "@sberbusiness/icons-next";
174
+
175
+ export const StatesExample = () => (
176
+ <div style={{ display: "flex", alignItems: "center", gap: "32px" }}>
177
+ <div>
178
+ <div style={{ marginBottom: "8px", fontSize: "16px", fontWeight: "700" }}>Default</div>
179
+ <IconWrapper>
180
+ <SettingsStrokeSrvIcon20 paletteIndex={5} />
181
+ </IconWrapper>
182
+ </div>
183
+ <div>
184
+ <div style={{ marginBottom: "8px", fontSize: "16px", fontWeight: "700" }}>Active</div>
185
+ <IconWrapper active>
186
+ <SettingsStrokeSrvIcon20 paletteIndex={5} />
187
+ </IconWrapper>
188
+ </div>
189
+ <div>
190
+ <div style={{ marginBottom: "8px", fontSize: "16px", fontWeight: "700" }}>Disabled</div>
191
+ <IconWrapper disabled>
192
+ <SettingsStrokeSrvIcon20 paletteIndex={5} />
193
+ </IconWrapper>
194
+ </div>
195
+ </div>
196
+ );
197
+ ```
198
+