@sberbusiness/triplex-next-mcp-server 1.53.0 → 1.54.0

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 CHANGED
@@ -45,6 +45,7 @@ MCP-сервер для дизайн-системы Triplex. Предостав
45
45
  | `get_tokens(component?, group?)` | Дизайн-токены: индекс групп, токены компонента или группа целиком — с путями `Группа.Токен` и значениями тем |
46
46
  | `list_icons(query?, size?, limit?)` | Иконки из `@sberbusiness/icons-next` с фильтрами по подстроке в имени и размеру (защита от галлюцинаций имён) |
47
47
  | `get_guidelines` | Гайдлайны кодогенерации на Triplex для ИИ-агентов (правила `TPX-G-NN` из `docs/mcp/guidelines.md` triplex-next) |
48
+ | `get_release_notes(version?)` | Release notes указанной версии Triplex в исходном MDX. Без аргументов используется версия bundle. |
48
49
 
49
50
  Флаг `ai_ready: true` означает, что у компонента есть полноценная AI-документация
50
51
  (`*-AI.md` с frontmatter, секциями и примерами). Для остальных компонентов сервер
@@ -130,6 +131,12 @@ bundle/
130
131
  которая тянет `@sberbusiness/icons-next` с jsdelivr и собирает список имён
131
132
  экспортов с размерами.
132
133
 
134
+ ## Публикация MCP-сервера
135
+
136
+ `sync-bundle.yml` ежедневно проверяет последний стабильный релиз triplex-next,
137
+ синхронизирует bundle и выпускает стабильную версию MCP с тем же номером.
138
+ `release.yml` собирает пакет с тега, публикует его в npm и прикладывает архив к GitHub Release.
139
+
133
140
  ## Лицензия
134
141
 
135
142
  MIT
@@ -49,7 +49,7 @@ version: "1.0"
49
49
  | Prop | По умолчанию | Назначение и ограничения |
50
50
  |---|---|---|
51
51
  | `type` | `EChipType.TYPE_1` | Визуальный тип `TYPE_1` / `TYPE_2` передаётся в `ChipSelectTarget`. |
52
- | `disabled` | `false` | Убирает target из tab-порядка и блокирует pointer-события стилями `Chip`; ограничения см. в Accessibility. |
52
+ | `disabled` | `false` | Убирает target из tab-порядка, блокирует pointer-события стилями `Chip` и отключает кнопку сброса; ограничения см. в Accessibility. |
53
53
  | `onOpen`, `onClose` | — | Уведомления о смене открытости без аргументов. На первом рендере не вызываются. |
54
54
  | `closeOnTab` | `false` | Наследуется от `SelectExtendedField` и закрывает список при всплывающем Tab, в том числе из dropdown. Сам target закрывает список по Tab независимо от этого флага. |
55
55
  | `className`, HTML-атрибуты | — | Передаются корневому `<div>` `SelectExtendedField`, не вложенному чипсу. |
@@ -63,7 +63,7 @@ version: "1.0"
63
63
 
64
64
  Унаследованный `dropdownProps` присутствует в типе, но текущая реализация не
65
65
  передаёт его в `SelectExtendedFieldDropdownDefault`. Не используй его для настройки
66
- списка. Не меняй это поведение в рамках документирования без отдельной задачи.
66
+ списка.
67
67
 
68
68
  ---
69
69
 
@@ -114,8 +114,9 @@ version: "1.0"
114
114
  `aria-controls` / `aria-activedescendant`. Переданный `aria-label` или
115
115
  `aria-labelledby` попадает на корневой `<div>`, не на target: не считай его
116
116
  заменой видимого названия чипса.
117
- - `disabled` задаёт класс и `tabIndex={-1}` на `Chip`. Это не нативный `disabled`:
118
- программно отправленные события не защищены проверкой флага в обработчиках.
117
+ - `disabled` задаёт класс и `tabIndex={-1}` на `Chip` и нативный `disabled` на кнопку
118
+ сброса. Сам target — не нативная кнопка: программно отправленные события
119
+ не защищены проверкой флага в обработчиках.
119
120
  Не используй такое состояние как защиту бизнес-логики.
120
121
  - В selected-состоянии вложена нативная `ChipClearButton`; Enter и Space на ней
121
122
  не всплывают в обработчик открытия target. Доступное имя кнопки сброса текущая
@@ -129,7 +130,7 @@ version: "1.0"
129
130
  ## Связанные компоненты
130
131
 
131
132
  - `Chip` — базовый компонент семейства, стили размеров, типов и выбранного состояния.
132
- - `ChipSelectTarget` — самостоятельный публичный target с кнопкой сброса, стрелкой
133
+ - [ChipSelectTarget](ChipSelectTarget-ai.md) — самостоятельный публичный target с кнопкой сброса, стрелкой
133
134
  и обработкой клавиатуры; рендерится внутри `ChipSelect`.
134
135
  - `ChipMultiselect` — альтернатива для нескольких значений и собственного dropdown-контента.
135
136
  - `SelectExtendedField` — рендерится внутри, владеет открытостью и корневым ref.
@@ -167,6 +168,7 @@ version: "1.0"
167
168
  | 2026-10-19 | Создан документ (TRI-131): описаны управляемый выбор, граница наследуемых props, токены и существующие accessibility-ограничения; собственные props оформлены JSDoc, дополнены unit-тесты контракта. |
168
169
  | 2026-10-19 | Удалена ссылка на задачу об ограничении доступности, сохранён номер TRI-139. |
169
170
  | 2026-10-21 | Переход на `Puller` (TRIPLEX-961): на мобильной ширине список открывается в нижней шторке `Puller`. |
171
+ | 2026-11-06 | Исправлено (TRI-0): при `disabled` кнопка сброса отключена и недоступна с клавиатуры. |
170
172
 
171
173
  ---
172
174
 
@@ -0,0 +1,388 @@
1
+ ---
2
+ component: ChipSelectTarget
3
+ category: Chips
4
+ related: [ChipSelect, ChipSuggestTarget, Chip, SelectExtendedField]
5
+ tokens: []
6
+ stories: stories/Chips/ChipSelect.stories.tsx
7
+ version: "1.0"
8
+ ---
9
+
10
+ # ChipSelectTarget
11
+
12
+ ## Назначение
13
+
14
+ Публичный управляющий элемент `ChipSelect`: показывает подпись фильтра,
15
+ стрелку списка либо кнопку очистки и запрашивает изменение открытости.
16
+ Собственного состояния, списка опций и логики выбора у target нет.
17
+
18
+ Используй когда: нужна своя композиция с чипсом и управляемыми `opened` /
19
+ `setOpened`, например через `SelectExtendedField.renderTarget`.
20
+ Для готового фильтра с опциями и мобильным списком используй `ChipSelect`.
21
+ Для target, связанного с контекстом поиска `Suggest`, нужен `ChipSuggestTarget`.
22
+
23
+ Публичные импорты:
24
+
25
+ ```tsx
26
+ import { ChipSelectTarget, type IChipSelectTargetProps } from "@sberbusiness/triplex-next";
27
+ ```
28
+
29
+ В отличие от `ChipSuggestTarget`, компонент не требует provider: открытость,
30
+ выбранное состояние и подпись полностью задаёт потребитель.
31
+
32
+ ---
33
+
34
+ ## Варианты и props
35
+
36
+ `IChipSelectTargetProps` расширяет `IChipProps` и добавляет три обязательных prop.
37
+ Точные типы унаследованных атрибутов описаны в исходнике `Chip`.
38
+
39
+ ### Управление
40
+
41
+ | Prop | Обязательность / по умолчанию | Назначение и ограничения |
42
+ |---|---|---|
43
+ | `opened` | Обязателен | Текущая открытость списка. Управляет стрелкой и исходным `aria-expanded` |
44
+ | `setOpened` | Обязателен | Получает новое логическое значение: клик переключает его, Enter / Space открывают закрытый список, Tab закрывает открытый |
45
+ | `onClear` | Обязателен | Вызывается без аргументов встроенной кнопкой очистки. Потребитель сбрасывает своё значение, `selected` и подпись |
46
+ | `selected` | `false` | Управляемое оформление выбора. При `true` вместо стрелки появляется очистка |
47
+ | `children` | — | Подпись фильтра или выбранной опции; target не вычисляет и не форматирует её |
48
+ | `onClick` | — | Получает React mouse event после `setOpened(!opened)` |
49
+ | `onKeyDown` | — | Получает все дошедшие до target события клавиатуры после встроенной обработки, кроме Enter / Space на встроенной очистке |
50
+
51
+ `setOpened` — запрос к потребителю, а не локальный setter компонента. Пока
52
+ потребитель не передаст новый `opened`, ARIA и стрелка сохранят прежнее состояние.
53
+ `onClear` не меняет `selected` и не закрывает список автоматически.
54
+ `preventDefault()` в пользовательском обработчике не отменяет уже сделанный
55
+ запрос изменения открытости.
56
+
57
+ ### Внешний вид и атрибуты
58
+
59
+ | Prop или группа | По умолчанию | Назначение и ограничения |
60
+ |---|---|---|
61
+ | `size` | `EComponentSize.MD` | SM / MD / LG; передаётся в `Chip`, стрелку и кнопку очистки |
62
+ | `type` | `EChipType.TYPE_1` | Визуальный тип `Chip`: TYPE_1 / TYPE_2 |
63
+ | `disabled` | `false` | Отключает target и встроенную очистку; ограничения — в Accessibility |
64
+ | `prefix` | — | Унаследованный слот перед подписью |
65
+ | `postfix` | Стрелка либо очистка | Переданное значение заменяет автоматический postfix; явно переданный `undefined` тоже заменяет его |
66
+ | `showNotificationIcon` | `false` | Значок `Badge.Dot` через `Chip` |
67
+ | `className`, `style`, `id`, `data-*`, `aria-*`, остальные span-атрибуты | — | Передаются в корневой `<span>`; `className` объединяется с классами `Chip` |
68
+ | `ref` | — | Указывает на корневой `HTMLSpanElement`, не на кнопку очистки или корень селекта |
69
+
70
+ Spread расположен после `postfix` и `aria-expanded`: потребитель может заменить
71
+ слот или ARIA-атрибут, а также унаследованные `role` / `tabIndex`. Сохраняй их
72
+ согласованность с настоящим состоянием списка. Для слотов действуют правила
73
+ `Chip`: `null` / `false` могут оставить класс отступов без содержимого.
74
+
75
+ ---
76
+
77
+ ## Дизайн-токены
78
+
79
+ Собственных стилей и токенов у target нет, поэтому `tokens: []`.
80
+ Внешний вид наследуется от `Chip`: см. [Chip-ai.md](Chip-ai.md#дизайн-токены).
81
+
82
+ ---
83
+
84
+ ## Инварианты
85
+
86
+ - Сохраняются именованные экспорты `ChipSelectTarget` и `IChipSelectTargetProps`
87
+ через `Chip/ChipSelect/index.ts` и `Chip/index.ts`, имена и типы props.
88
+ - `forwardRef<HTMLSpanElement>` обязателен; корень и ref-target остаются
89
+ `<span>` `Chip`. `displayName` — `"ChipSelectTarget"`.
90
+ - Открытость управляется только `opened` / `setOpened`; не добавлять локальный
91
+ state или зависимость от контекста другого селекта.
92
+ - Enter / Space открывают только закрытый список, а не переключают его.
93
+ Tab закрывает открытый список без отмены стандартного перемещения фокуса.
94
+ - `onClick` вызывается после setter, `onKeyDown` — после встроенной обработки событий, дошедших до target.
95
+ Очистка вызывает только `onClear()` и останавливает всплытие клика и Enter / Space.
96
+ - `selected` влияет на автоматический postfix, `onClear` не получает DOM-событие.
97
+ - Порядок spread, размеры и типы `Chip`, токены и story IDs сохраняются.
98
+ Код остаётся совместимым с React 17.
99
+
100
+ ---
101
+
102
+ ## Accessibility
103
+
104
+ - По умолчанию корень — `<span role="button" tabIndex={0}>`, не combobox.
105
+ Имя задают `children`, `aria-label` или `aria-labelledby` потребителя.
106
+ - `aria-expanded` отражает `opened`, если не переопределён явно.
107
+ `aria-controls` и `aria-haspopup` автоматически не создаются.
108
+ - Enter / Space на закрытом target вызывают `preventDefault()` и открывают список.
109
+ На открытом target они не закрывают список. `Chip` дополнительно отменяет
110
+ прокрутку страницы на Space. Tab закрывает открытый список, не отменяя Tab.
111
+ - Escape, клики вне списка, навигация по опциям и перенос фокуса принадлежат
112
+ композиции селекта, а не target. Сам target фокусом не управляет.
113
+ - Встроенная очистка — нативная кнопка: Enter / Space активируют её нативный
114
+ click и вызывают `onClear`.
115
+ - У встроенной очистки нет доступного имени, отдельного prop для его
116
+ передачи тоже нет (TRI-139). Для своей именованной очистки доступен `postfix`.
117
+ Такая кнопка, как и встроенная, должна останавливать всплытие click и keydown
118
+ Enter / Space: иначе их обработает target, а `preventDefault()` может отменить
119
+ нативный click кнопки.
120
+ - `disabled` ставит `tabIndex=-1` и CSS `pointer-events: none` на `Chip`
121
+ и нативный `disabled` на встроенную кнопку очистки. В обработчиках target
122
+ флаг не проверяется: программно отправленные события не защищены.
123
+ Не передавай `tabIndex`: он перекрывает `-1`, target снова получит фокус,
124
+ и Enter / Space откроют список.
125
+ Пользовательский `postfix` потребитель отключает сам.
126
+ - `selected` сам не задаёт `aria-pressed`. Фокус оформляется через `Chip.Shadow_Focus`.
127
+
128
+ ---
129
+
130
+ ## Связанные компоненты
131
+
132
+ - [ChipSelect](ChipSelect-ai.md) — готовый родитель: владеет значением через props
133
+ потребителя, получает открытость от `SelectExtendedField` и рендерит target.
134
+ - [ChipSuggestTarget](ChipSuggestTarget-ai.md) — альтернатива для композиции
135
+ с `SuggestContext`: читает выбор и открытость из контекста и иначе обрабатывает клавиши.
136
+ - [Chip](Chip-ai.md) — непосредственно рендерится внутри; источник `IChipProps`,
137
+ span-семантики и всех визуальных состояний.
138
+ - [SelectExtendedField](../SelectExtendedField/SelectExtendedField-ai.md) — предоставляет
139
+ `opened` / `setOpened` в `renderTarget`.
140
+ - `ChipClearButton` и `ChipDropdownArrow` — автоматические варианты postfix;
141
+ отдельная кнопка очистки принимает атрибуты `ButtonIcon`, включая `aria-label`.
142
+
143
+ ---
144
+
145
+ ## Stories
146
+
147
+ Основные истории: `stories/Chips/ChipSelect.stories.tsx`
148
+ Файлы примеров: `stories/Chips/examples/ChipSelect/`
149
+
150
+ Используются истории родителя: он рендерит этот target.
151
+ Отдельный stories-файл атому не требуется.
152
+
153
+ | Story | Example file | Что демонстрирует |
154
+ |---|---|---|
155
+ | `Playground` | `PlaygroundExample.tsx` | Тип, размер, подписи и disabled |
156
+ | `Default` | `DefaultExample.tsx` | Подпись, стрелка, выбор и очистка |
157
+ | `Sizes` | `SizesExample.tsx` | SM / MD / LG target |
158
+ | `States` | `StatesExample.tsx` | Selected и disabled |
159
+ | `WithNotificationIcon` | `WithNotificationIconExample.tsx` | Значок у опции списка, не у target; отдельный скриншот-прогон пропущен |
160
+ | `WithCustomDisplayedValue` | `WithCustomDisplayedValueExample.tsx` | Подпись выбранного значения задаёт родитель |
161
+ | `VisualTests` | `VisualTestsExample.tsx` | Выбранные target трёх размеров; play открывает список у unselected target |
162
+
163
+ У семейства есть Linux/CI baseline на `xs` / `xl` в `__screenshots__/`;
164
+ фокус базового чипса проверяет `VisualTests` в stories `Chip`.
165
+ Контракт target проверяет `src/components/Chip/__tests__/ChipSelectTarget.test.tsx`,
166
+ интеграцию выбора — `ChipSelect.test.tsx`.
167
+
168
+ ---
169
+
170
+ ## История изменений
171
+
172
+ | Дата | Изменение |
173
+ |---|---|
174
+ | 2026-10-29 | Создан документ. |
175
+
176
+ ---
177
+
178
+ ## Примеры использования
179
+
180
+ ### Default
181
+
182
+ `stories/Chips/examples/ChipSelect/DefaultExample.tsx`
183
+
184
+ ```tsx
185
+ import React, { useState } from "react";
186
+ import { ChipSelect, EComponentSize, type ISelectFieldOption } from "@sberbusiness/triplex-next";
187
+
188
+ const demoOptions: ISelectFieldOption[] = [
189
+ { id: "1", value: "option1", label: "Первая опция" },
190
+ { id: "2", value: "option2", label: "Вторая опция" },
191
+ { id: "3", value: "option3", label: "Третья опция" },
192
+ { id: "4", value: "option4", label: "Четвертая опция" },
193
+ { id: "5", value: "option5", label: "Пятая опция" },
194
+ { id: "6", value: "option6", label: "Шестая опция" },
195
+ ];
196
+
197
+ export const DefaultExample = () => {
198
+ const [selected, setSelected] = useState<ISelectFieldOption | undefined>();
199
+ return (
200
+ <ChipSelect
201
+ label="Select label"
202
+ size={EComponentSize.MD}
203
+ options={demoOptions}
204
+ value={selected}
205
+ onChange={setSelected}
206
+ clearSelected={() => setSelected(undefined)}
207
+ />
208
+ );
209
+ };
210
+ ```
211
+
212
+ ### Sizes
213
+
214
+ `stories/Chips/examples/ChipSelect/SizesExample.tsx`
215
+
216
+ ```tsx
217
+ import React, { useState } from "react";
218
+ import { ChipSelect, EComponentSize, type ISelectFieldOption } from "@sberbusiness/triplex-next";
219
+
220
+ const demoOptions: ISelectFieldOption[] = [
221
+ { id: "1", value: "option1", label: "Первая опция" },
222
+ { id: "2", value: "option2", label: "Вторая опция" },
223
+ { id: "3", value: "option3", label: "Третья опция" },
224
+ { id: "4", value: "option4", label: "Четвертая опция" },
225
+ { id: "5", value: "option5", label: "Пятая опция" },
226
+ { id: "6", value: "option6", label: "Шестая опция" },
227
+ ];
228
+
229
+ export const SizesExample = () => {
230
+ const [selectedSM, setSelectedSM] = useState<ISelectFieldOption | undefined>();
231
+ const [selectedMD, setSelectedMD] = useState<ISelectFieldOption | undefined>();
232
+ const [selectedLG, setSelectedLG] = useState<ISelectFieldOption | undefined>();
233
+
234
+ return (
235
+ <div style={{ display: "flex", flexDirection: "column", gap: "24px" }}>
236
+ <div>
237
+ <div style={{ marginBottom: "8px", fontSize: "16px", fontWeight: "700" }}>SM</div>
238
+ <ChipSelect
239
+ options={demoOptions}
240
+ value={selectedSM}
241
+ onChange={setSelectedSM}
242
+ clearSelected={() => setSelectedSM(undefined)}
243
+ size={EComponentSize.SM}
244
+ label="Select label"
245
+ />
246
+ </div>
247
+ <div>
248
+ <div style={{ marginBottom: "8px", fontSize: "16px", fontWeight: "700" }}>MD</div>
249
+ <ChipSelect
250
+ options={demoOptions}
251
+ value={selectedMD}
252
+ onChange={setSelectedMD}
253
+ clearSelected={() => setSelectedMD(undefined)}
254
+ size={EComponentSize.MD}
255
+ label="Select label"
256
+ />
257
+ </div>
258
+ <div>
259
+ <div style={{ marginBottom: "8px", fontSize: "16px", fontWeight: "700" }}>LG</div>
260
+ <ChipSelect
261
+ options={demoOptions}
262
+ value={selectedLG}
263
+ onChange={setSelectedLG}
264
+ clearSelected={() => setSelectedLG(undefined)}
265
+ size={EComponentSize.LG}
266
+ label="Select label"
267
+ />
268
+ </div>
269
+ </div>
270
+ );
271
+ };
272
+ ```
273
+
274
+ ### States
275
+
276
+ `stories/Chips/examples/ChipSelect/StatesExample.tsx`
277
+
278
+ ```tsx
279
+ import React, { useState } from "react";
280
+ import { ChipSelect, EComponentSize, type ISelectFieldOption } from "@sberbusiness/triplex-next";
281
+
282
+ const demoOptions: ISelectFieldOption[] = [
283
+ { id: "1", value: "option1", label: "Первая опция" },
284
+ { id: "2", value: "option2", label: "Вторая опция" },
285
+ { id: "3", value: "option3", label: "Третья опция" },
286
+ { id: "4", value: "option4", label: "Четвертая опция" },
287
+ { id: "5", value: "option5", label: "Пятая опция" },
288
+ { id: "6", value: "option6", label: "Шестая опция" },
289
+ ];
290
+
291
+ export const StatesExample = () => {
292
+ const [selectedWithValue, setSelectedWithValue] = useState<ISelectFieldOption | undefined>(demoOptions[0]);
293
+ const [selectedDisabled, setSelectedDisabled] = useState<ISelectFieldOption | undefined>(demoOptions[1]);
294
+
295
+ return (
296
+ <div style={{ display: "flex", flexDirection: "column", gap: "24px" }}>
297
+ <div style={{ display: "flex", flexDirection: "column", gap: "8px" }}>
298
+ <div style={{ marginBottom: "8px", fontSize: "16px", fontWeight: "700" }}>Selected</div>
299
+ <ChipSelect
300
+ label="Select label"
301
+ options={demoOptions}
302
+ value={selectedWithValue}
303
+ onChange={setSelectedWithValue}
304
+ clearSelected={() => setSelectedWithValue(undefined)}
305
+ size={EComponentSize.MD}
306
+ />
307
+ </div>
308
+ <div style={{ display: "flex", flexDirection: "column", gap: "8px" }}>
309
+ <div style={{ marginBottom: "8px", fontSize: "16px", fontWeight: "700" }}>Disabled</div>
310
+ <ChipSelect
311
+ label="Select label"
312
+ options={demoOptions}
313
+ value={selectedDisabled}
314
+ onChange={setSelectedDisabled}
315
+ clearSelected={() => setSelectedDisabled(undefined)}
316
+ disabled
317
+ size={EComponentSize.MD}
318
+ />
319
+ </div>
320
+ </div>
321
+ );
322
+ };
323
+ ```
324
+
325
+ ### WithNotificationIcon
326
+
327
+ `stories/Chips/examples/ChipSelect/WithNotificationIconExample.tsx`
328
+
329
+ ```tsx
330
+ import React, { useState } from "react";
331
+ import { ChipSelect, EComponentSize, type ISelectFieldOption } from "@sberbusiness/triplex-next";
332
+
333
+ const options: ISelectFieldOption[] = [
334
+ { id: "1", value: "option1", label: "Первая опция" },
335
+ { id: "2", value: "option2", label: "Вторая опция", showNotificationIcon: true },
336
+ { id: "3", value: "option3", label: "Третья опция" },
337
+ { id: "4", value: "option4", label: "Четвертая опция" },
338
+ ];
339
+
340
+ export const WithNotificationIconExample = () => {
341
+ const [selected, setSelected] = useState<ISelectFieldOption | undefined>();
342
+
343
+ return (
344
+ <ChipSelect
345
+ size={EComponentSize.MD}
346
+ label="Select label"
347
+ options={options}
348
+ value={selected}
349
+ onChange={setSelected}
350
+ clearSelected={() => setSelected(undefined)}
351
+ />
352
+ );
353
+ };
354
+ ```
355
+
356
+ ### WithCustomDisplayedValue
357
+
358
+ `stories/Chips/examples/ChipSelect/WithCustomDisplayedValueExample.tsx`
359
+
360
+ ```tsx
361
+ import React, { useState } from "react";
362
+ import { ChipSelect, EComponentSize, type ISelectFieldOption } from "@sberbusiness/triplex-next";
363
+
364
+ const demoOptions: ISelectFieldOption[] = [
365
+ { id: "1", value: "option1", label: "Первая опция" },
366
+ { id: "2", value: "option2", label: "Вторая опция" },
367
+ { id: "3", value: "option3", label: "Третья опция" },
368
+ { id: "4", value: "option4", label: "Четвертая опция" },
369
+ { id: "5", value: "option5", label: "Пятая опция" },
370
+ { id: "6", value: "option6", label: "Шестая опция" },
371
+ ];
372
+
373
+ export const WithCustomDisplayedValueExample = () => {
374
+ const [selected, setSelected] = useState<ISelectFieldOption | undefined>(demoOptions[0]);
375
+ return (
376
+ <ChipSelect
377
+ label="Select label"
378
+ options={demoOptions}
379
+ value={selected}
380
+ onChange={setSelected}
381
+ clearSelected={() => setSelected(undefined)}
382
+ displayedValue="Displayed value"
383
+ size={EComponentSize.MD}
384
+ />
385
+ );
386
+ };
387
+ ```
388
+
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  component: ChipSuggest
3
3
  category: Chips
4
- related: [Chip, ChipSelect, Suggest, Dropdown, FormField]
4
+ related: [ChipSuggestTarget, Chip, ChipSelect, Suggest, Dropdown, FormField]
5
5
  tokens: []
6
6
  stories: stories/Chips/ChipSuggest.stories.tsx
7
7
  version: "1.0"
@@ -57,7 +57,7 @@ version: "1.0"
57
57
  |---|---|---|---|
58
58
  | `type` | `EChipType` | `EChipType.TYPE_1` (дефолт `Chip`) | Тип чипса. Задавать только здесь: `targetProps.type` не работает — см. «Инварианты» |
59
59
  | `displayedValue` | `React.ReactNode` | — | Что показать на чипсе вместо `value.label`. Учитывается только при выбранном `value` |
60
- | `targetProps` | `IChipSuggestTargetProps<T>` | — | Props target-элемента. Через него передаётся `clearSelected`, а также props `Chip` (`disabled`, `className`, `data-*`, `onClick`, `size`). Два исключения — см. «Инварианты»: `targetProps.type` не применяется никогда, `targetProps.onKeyDown` вызывается только на `Enter`/`Space` |
60
+ | `targetProps` | `IChipSuggestTargetProps<T>` | — | Props target-элемента. Через него передаётся `clearSelected`, а также props `Chip` (`disabled`, `className`, `data-*`, `onClick`, `size`). Исключение — см. «Инварианты»: `targetProps.type` не применяется никогда |
61
61
  | `dropdownProps` | `Omit<IChipSuggestDropdownProps<T>, "targetRef">` | — | Props выпадающего списка: props `Dropdown` (`onOpen`, `onClose`, `pullerProps`, …) плюс `focusTrapProps`. `targetRef` проставляет сам `ChipSuggest` |
62
62
 
63
63
  `className` попадает на корневой `<div>` (рядом с внутренним классом `chipGroupItem`),
@@ -106,23 +106,15 @@ version: "1.0"
106
106
  - **Известное ограничение, а не проектное решение:** `type` передаётся target-элементу
107
107
  **после** спреда `targetProps`, поэтому `targetProps.type` не применяется никогда — даже
108
108
  когда сам `type` не задан (тогда `Chip` уходит в свой дефолт). Тип чипса задавай prop'ом
109
- `type` на самом `ChipSuggest`. Починка меняет наблюдаемое поведение, поэтому делается
110
- отдельной задачей и с записью в release notes; тестом это поведение намеренно не
111
- зафиксировано, чтобы падающий тест не выглядел поломкой контракта при починке.
112
- Обрати внимание на асимметрию: `size` передаётся **до** спреда `targetProps`,
113
- поэтому `targetProps.size` как раз работает.
114
- - **Известное ограничение, а не проектное решение:** `ChipSuggestTarget.handleKeyDown`
115
- вызывает `targetProps.onKeyDown` только внутри ветки `Enter`/`Space`, поэтому на всех
116
- остальных клавишах (стрелки, `Tab`, печатные символы) обработчик потребителя не
117
- вызывается. Не рассчитывай на `targetProps.onKeyDown` для навигации по стрелкам.
118
- Починка меняет наблюдаемое поведение → отдельная задача с записью в release notes.
109
+ `type` на самом `ChipSuggest`. При этом `size` передаётся **до** спреда `targetProps`,
110
+ поэтому `targetProps.size` работает.
119
111
  - `prefix` намеренно вырезается из props и не доходит до корневого `<div>`: он приходит
120
112
  из `React.HTMLAttributes` как RDFa-атрибут (то есть на `<div>` был бы валиден), но
121
113
  `ChipSuggest` его не поддерживает — `ChipSuggestTarget` задаёт только `postfix`.
122
114
  - `ChipSuggestDesktopDropdownField` — единственный компонент папки, оставленный
123
115
  `React.FC` без `forwardRef`: он не экспортируется из barrel, а очевидного ref-таргета
124
116
  у него нет (корень — `FormField`). Осознанное исключение из общего правила
125
- `docs/ai/codestyle.md`, а не недосмотр; менять — отдельной задачей.
117
+ `docs/ai/codestyle.md`.
126
118
  - `dropdownProps.targetRef` исключён из типа: ссылку на корневой `<div>` подставляет сам
127
119
  `ChipSuggest`, иначе список потеряет привязку к чипсу.
128
120
  - `ChipSuggestDropdown` обязан класть ссылку на себя в `dropdownRef` из `SuggestContext` —
@@ -203,9 +195,9 @@ version: "1.0"
203
195
  `DropdownList` и `DropdownListItem`.
204
196
  - `FormField` — из его частей (`FormFieldLabel`, `FormFieldInput`, `FormFieldPostfix`,
205
197
  `FormFieldClear`) собрано поле фильтрации внутри десктопного списка.
206
- - `ChipSuggestTarget` — сам чипс-триггер. Экспортируется из barrel, но самостоятельного
198
+ - [ChipSuggestTarget](ChipSuggestTarget-ai.md) — сам чипс-триггер. Экспортируется из barrel, но самостоятельного
207
199
  применения не имеет: `value` и `dropdownOpen` берёт из `SuggestContext`, поэтому работает
208
- только внутри `Suggest`. Отдельного AI.md не имеет.
200
+ только внутри `Suggest`.
209
201
  - `ChipSuggestDropdown` — выпадающий список компонента: `FocusTrap` + поле фильтрации +
210
202
  `DropdownList` на десктопе, `SuggestMobileDropdownContent` на мобильной ширине. Из barrel
211
203
  не экспортируется (наружу уходит только тип props), отдельного AI.md не имеет.
@@ -243,6 +235,7 @@ version: "1.0"
243
235
  |---|---|
244
236
  | 2026-09-07 | Создан документ (AI-рефакторинг ChipSuggest, TRI-134) |
245
237
  | 2026-10-21 | **Breaking** (TRIPLEX-961): prop `mobileViewProps` в `dropdownProps` переименован в `pullerProps`. |
238
+ | 2026-11-06 | Исправлено (TRI-0): `targetProps.onKeyDown` вызывается для всех клавиш, а не только для `Enter`/`Space`; `targetProps.disabled` отключает встроенную кнопку очистки. |
246
239
 
247
240
  ---
248
241