@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 +7 -0
- package/bundle/components/ChipSelect.md +7 -5
- package/bundle/components/ChipSelectTarget.md +388 -0
- package/bundle/components/ChipSuggest.md +8 -15
- package/bundle/components/ChipSuggestTarget.md +506 -0
- package/bundle/components/Dropdown.md +2 -1
- package/bundle/components/MultiselectField.md +2 -57
- package/bundle/components/Portal.md +5 -2
- package/bundle/components/Puller.md +85 -90
- package/bundle/components/SelectExtendedField.md +6 -4
- package/bundle/components/SelectField.md +4 -2
- package/bundle/manifest.json +19 -2
- package/bundle/release-notes/1.53.0.mdx +1 -1
- package/bundle/release-notes/1.54.0.mdx +38 -0
- package/bundle/release-notes/1.55.0.mdx +7 -0
- package/bundle/tokens.json +1 -1
- package/dist/index.js +39 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
|
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
|
|
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
|
-
-
|
|
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`).
|
|
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
|
-
|
|
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
|
-
-
|
|
198
|
+
- [ChipSuggestTarget](ChipSuggestTarget-ai.md) — сам чипс-триггер. Экспортируется из barrel, но самостоятельного
|
|
207
199
|
применения не имеет: `value` и `dropdownOpen` берёт из `SuggestContext`, поэтому работает
|
|
208
|
-
только внутри `Suggest`.
|
|
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
|
|