@sberbusiness/triplex-next-mcp-server 1.40.0-beta → 1.42.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/bundle/components/AlertProcess.md +460 -0
- package/bundle/components/Amount.md +145 -169
- package/bundle/components/Badge.md +155 -219
- package/bundle/components/Body.md +108 -116
- package/bundle/components/Calendar.md +183 -368
- package/bundle/components/CarouselExtended.md +355 -194
- package/bundle/components/Chip.md +310 -314
- package/bundle/components/ComposedKeyDownListener.md +323 -0
- package/bundle/components/FooterPage.md +2 -1
- package/bundle/components/FormField.md +468 -232
- package/bundle/components/HeaderPage.md +2 -1
- package/bundle/components/IconWrapper.md +142 -89
- package/bundle/components/Island.md +303 -123
- package/bundle/components/KeyDownListener.md +152 -100
- package/bundle/components/LoaderMiddle.md +265 -0
- package/bundle/components/LoaderScreen.md +178 -81
- package/bundle/components/LoaderSmall.md +230 -0
- package/bundle/components/MaskedField.md +481 -0
- package/bundle/components/MediaWidth.md +246 -108
- package/bundle/components/MobileView.md +192 -68
- package/bundle/components/ModalWindow.md +3 -1
- package/bundle/components/Page.md +3 -1
- package/bundle/components/Pagination.md +20 -0
- package/bundle/components/SelectExtendedField.md +691 -236
- package/bundle/components/ThemeProvider.md +305 -117
- package/bundle/components/Tooltip.md +421 -181
- package/bundle/guides/commits.md +54 -9
- package/bundle/guides/context.md +26 -0
- package/bundle/guides/stories.md +2 -0
- package/bundle/guides/template.md +1 -1
- package/bundle/manifest.json +63 -18
- package/bundle/release-notes/1.41.0.mdx +76 -0
- package/bundle/release-notes/1.41.1.mdx +18 -0
- package/bundle/release-notes/1.42.0.mdx +88 -0
- package/bundle/release-notes/1.43.0.mdx +7 -0
- package/package.json +1 -1
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
---
|
|
2
|
+
component: ComposedKeyDownListener
|
|
3
|
+
category: KeyDownListener
|
|
4
|
+
related: [KeyDownListener, TriggerClickOnKeyDownEvent, SelectExtendedField, SliderExtended]
|
|
5
|
+
tokens: []
|
|
6
|
+
stories: stories/ComposedKeyDownListener/ComposedKeyDownListener.stories.tsx
|
|
7
|
+
version: "1.0"
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# ComposedKeyDownListener
|
|
11
|
+
|
|
12
|
+
## Назначение
|
|
13
|
+
|
|
14
|
+
Сервисный headless-компонент, композитор `KeyDownListener`. Принимает массив
|
|
15
|
+
конфигураций `keyDownListeners` и рендерит по одному `KeyDownListener` на элемент
|
|
16
|
+
массива — соседями к `children`. Каждый слушатель подписывается на `keydown` на
|
|
17
|
+
`window` независимо, поэтому компонент — это только способ описать несколько
|
|
18
|
+
горячих клавиш одним объявлением вместо ручного вложения нескольких
|
|
19
|
+
`KeyDownListener`.
|
|
20
|
+
|
|
21
|
+
Собственной разметки не рендерит: `children` выводятся как есть, без обёртки;
|
|
22
|
+
без `children` (и при любом наборе слушателей) DOM остаётся пустым — сами
|
|
23
|
+
`KeyDownListener` рендерят `null`.
|
|
24
|
+
|
|
25
|
+
Используй когда: у экрана или оверлея несколько глобальных горячих клавиш с
|
|
26
|
+
разными обработчиками — `Enter` подтверждает, `Esc` отменяет, стрелки листают.
|
|
27
|
+
|
|
28
|
+
Не используй когда:
|
|
29
|
+
- Клавиша всего одна — достаточно `KeyDownListener` напрямую.
|
|
30
|
+
- Клавиши разные, но обработчик один — у `KeyDownListener` `eventKeyCode`
|
|
31
|
+
принимает массив кодов; различить нажатую можно по `event.keyCode`.
|
|
32
|
+
- Клавиша должна обрабатываться только внутри конкретного элемента — навесь
|
|
33
|
+
`onKeyDown` на сам элемент: слушатели здесь глобальные и сработают при вводе в
|
|
34
|
+
любое поле на странице.
|
|
35
|
+
- Нужно кликнуть по конкретной кнопке по нажатию клавиши — есть готовый
|
|
36
|
+
`TriggerClickOnKeyDownEvent`.
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Варианты и props
|
|
41
|
+
|
|
42
|
+
### Обязательные props
|
|
43
|
+
|
|
44
|
+
| Prop | Тип | Описание |
|
|
45
|
+
|---|---|---|
|
|
46
|
+
| `keyDownListeners` | `IKeyDownListenerProps[]` | Конфигурация слушателей. Из каждого элемента используются только `eventKeyCode` и `onMatch`; поле `children` элемента игнорируется — содержимое задаётся `children` самого композитора |
|
|
47
|
+
|
|
48
|
+
### Опциональные props
|
|
49
|
+
|
|
50
|
+
| Prop | Тип | По умолчанию | Описание |
|
|
51
|
+
|---|---|---|---|
|
|
52
|
+
| `children` | `React.ReactNode` | — | Рендерится как есть, без обёртки. При пустом `keyDownListeners` компонент сводится к `children` |
|
|
53
|
+
|
|
54
|
+
### Особенности поведения
|
|
55
|
+
|
|
56
|
+
- Слушатели рендерятся **соседями** к `children`
|
|
57
|
+
(`<>{listeners}{children}</>`), а не оборачивают их. Поэтому `children` не
|
|
58
|
+
зависят от длины и порядка массива: набор горячих клавиш можно менять между
|
|
59
|
+
рендерами, содержимое при этом не перемонтируется — DOM-узлы, значения
|
|
60
|
+
инпутов, фокус и анимации сохраняются.
|
|
61
|
+
- `onMatch` вызываются в порядке элементов `keyDownListeners`: слушатели
|
|
62
|
+
монтируются в порядке массива, а `window` вызывает обработчики в порядке
|
|
63
|
+
подписки. На нём завязан только порядок вызова — «перехватить» событие у
|
|
64
|
+
соседнего слушателя нельзя, все подписаны на `window` независимо.
|
|
65
|
+
- Несколько элементов массива могут слушать одну и ту же клавишу — сработают
|
|
66
|
+
все.
|
|
67
|
+
- Слушатели подписываются на `window` **до** `children`, потому что монтируются
|
|
68
|
+
раньше них. Если внутри `children` есть собственный глобальный
|
|
69
|
+
`keydown`-обработчик той же клавиши, `onMatch` сработает первым.
|
|
70
|
+
- Компонент ничего не мемоизирует: массив можно собирать инлайн в рендере.
|
|
71
|
+
Каждый `KeyDownListener` читает свежие `eventKeyCode` и `onMatch` из props в
|
|
72
|
+
момент события, переподписки на новые ссылки не происходит.
|
|
73
|
+
- `keyDownListeners` сопоставляются между рендерами по индексу (`key={index}`).
|
|
74
|
+
Для элементов конфигурации это безопасно: `KeyDownListener` не рендерит
|
|
75
|
+
разметку и не хранит внутренний state, поэтому переиспользование инстанса по
|
|
76
|
+
позиции наблюдаемого эффекта не даёт.
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Дизайн-токены
|
|
81
|
+
|
|
82
|
+
Собственных стилей и CSS-переменных нет — компонент не рендерит разметку.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Инварианты
|
|
87
|
+
|
|
88
|
+
- **Имя prop `keyDownListeners`, `children` и интерфейс
|
|
89
|
+
`IComposedKeyDownListenerProps`** (экспортируется из barrel
|
|
90
|
+
`src/components/KeyDownListener/index.ts`) — публичное API, не менять.
|
|
91
|
+
- **Элементы `keyDownListeners` типизированы `IKeyDownListenerProps`** —
|
|
92
|
+
переиспользование типа `KeyDownListener` осознанное; не заводить отдельный тип
|
|
93
|
+
ради «более узкого» `Pick`, это сузит публичный контракт.
|
|
94
|
+
- **Плоский рендер слушателей** — не возвращаться к вложению
|
|
95
|
+
(`reduce` с оборачиванием `children`): вложенность делает глубину дерева
|
|
96
|
+
зависимой от длины массива и перемонтирует `children` при её изменении.
|
|
97
|
+
- **Собственной разметки нет** — не добавлять обёртку (`div`, `span`) вокруг
|
|
98
|
+
`children`: потребители рассчитывают, что компонент прозрачен для layout,
|
|
99
|
+
в том числе внутри flex/grid-контейнеров.
|
|
100
|
+
- **Без `forwardRef`** — осознанное исключение, как и у `KeyDownListener`:
|
|
101
|
+
компонент не рендерит собственный host-элемент, форвардить `ref` некуда.
|
|
102
|
+
- Реализация остаётся React 17-совместимой (функциональный компонент без
|
|
103
|
+
React 18-only API): код синхронизируется в `release-0`.
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## Accessibility
|
|
108
|
+
|
|
109
|
+
- Собственной ARIA-семантики нет — компонент прозрачен для accessibility-дерева
|
|
110
|
+
и не рендерит фокусируемых элементов.
|
|
111
|
+
- Горячие клавиши глобальные: перехватываются независимо от позиции фокуса и не
|
|
112
|
+
анонсируются screen reader'ом. Действия, повешенные на клавиши, должны быть
|
|
113
|
+
доступны и обычным путём (кнопкой) — шорткат не единственный способ выполнить
|
|
114
|
+
действие.
|
|
115
|
+
- Компонент не проверяет, где находится фокус: `Enter` и `Esc` сработают и во
|
|
116
|
+
время ввода в поле. Если это нежелательно, фильтруй в `onMatch` по
|
|
117
|
+
`event.target`.
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## Связанные компоненты
|
|
122
|
+
|
|
123
|
+
- `KeyDownListener` (`src/components/KeyDownListener/KeyDownListener.tsx`) —
|
|
124
|
+
элементарный слушатель, из которого собирается композитор; там же описано
|
|
125
|
+
сравнение по устаревшему `event.keyCode` и константы `EVENT_KEY_CODES`.
|
|
126
|
+
- `TriggerClickOnKeyDownEvent` (`src/components/Triggers/`) — по нажатию клавиши
|
|
127
|
+
вызывает `click` на элементе из `targetRef`.
|
|
128
|
+
- `SelectExtendedField`, `SliderExtendedDot`, `SliderExtendedTrack` — потребители
|
|
129
|
+
`KeyDownListener` внутри библиотеки.
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## Stories
|
|
134
|
+
|
|
135
|
+
Основные истории: `stories/ComposedKeyDownListener/ComposedKeyDownListener.stories.tsx`
|
|
136
|
+
Файлы примеров: `stories/ComposedKeyDownListener/examples/`
|
|
137
|
+
|
|
138
|
+
| Story | Example file | Что демонстрирует |
|
|
139
|
+
|---|---|---|
|
|
140
|
+
| `Playground` | `Playground.tsx` | Набор клавиш выбирается чекбоксами: слушатели пересобираются, введённый текст сохраняется |
|
|
141
|
+
| `Default` | `Default.tsx` | Две клавиши с разными обработчиками: `Enter` подтверждает, `Esc` отменяет |
|
|
142
|
+
| `WithDynamicListeners` | `WithDynamicListeners.tsx` | Слушатель добавляется на лету — `children` не перемонтируются |
|
|
143
|
+
| `Example` | `Example.tsx` | Панель подтверждения: горячие клавиши смонтированы вместе с панелью, те же действия доступны кнопками |
|
|
144
|
+
|
|
145
|
+
Набор целиком исключён из скриншот-тестов (`testRunner: { skip: true }` в `meta`):
|
|
146
|
+
собственной разметки компонент не рендерит, на скриншот попадала бы только
|
|
147
|
+
обвязка примеров. Поведение слушателей покрыто unit-тестами.
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## История изменений
|
|
152
|
+
|
|
153
|
+
| Дата | Изменение |
|
|
154
|
+
|---|---|
|
|
155
|
+
| 2026-08-11 | Создан документ (TRI-112). Слушатели рендерятся соседями к `children` вместо вложения — смена длины `keyDownListeners` больше не перемонтирует содержимое (покрыто unit-тестом). Добавлен собственный набор stories по modern pattern; демонстрационная story `Example` перенесена из набора `KeyDownListener`. Публичный API не изменён. |
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## Примеры использования
|
|
160
|
+
|
|
161
|
+
### Default
|
|
162
|
+
|
|
163
|
+
`stories/ComposedKeyDownListener/examples/Default.tsx`
|
|
164
|
+
|
|
165
|
+
```tsx
|
|
166
|
+
import React, { useState } from "react";
|
|
167
|
+
import { ComposedKeyDownListener, EVENT_KEY_CODES } from "@sberbusiness/triplex-next";
|
|
168
|
+
|
|
169
|
+
export const Default = () => {
|
|
170
|
+
const [result, setResult] = useState("Ожидание нажатия клавиши");
|
|
171
|
+
|
|
172
|
+
const keyDownListeners = [
|
|
173
|
+
{ eventKeyCode: EVENT_KEY_CODES.ENTER, onMatch: () => setResult("Подтверждено по Enter") },
|
|
174
|
+
{ eventKeyCode: EVENT_KEY_CODES.ESCAPE, onMatch: () => setResult("Отменено по Esc") },
|
|
175
|
+
];
|
|
176
|
+
|
|
177
|
+
return (
|
|
178
|
+
<ComposedKeyDownListener keyDownListeners={keyDownListeners}>
|
|
179
|
+
<div style={{ display: "flex", flexDirection: "column", gap: "16px", maxWidth: "400px" }}>
|
|
180
|
+
<div>Две горячие клавиши с разными обработчиками: Enter подтверждает, Esc отменяет.</div>
|
|
181
|
+
<div style={{ border: "1px dashed rgb(125, 131, 138)", borderRadius: "4px", padding: "16px" }}>
|
|
182
|
+
{result}
|
|
183
|
+
</div>
|
|
184
|
+
</div>
|
|
185
|
+
</ComposedKeyDownListener>
|
|
186
|
+
);
|
|
187
|
+
};
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### WithDynamicListeners
|
|
191
|
+
|
|
192
|
+
`stories/ComposedKeyDownListener/examples/WithDynamicListeners.tsx`
|
|
193
|
+
|
|
194
|
+
```tsx
|
|
195
|
+
import React, { useState } from "react";
|
|
196
|
+
import { ComposedKeyDownListener, EVENT_KEY_CODES } from "@sberbusiness/triplex-next";
|
|
197
|
+
|
|
198
|
+
export const WithDynamicListeners = () => {
|
|
199
|
+
const [arrowsEnabled, setArrowsEnabled] = useState(false);
|
|
200
|
+
const [result, setResult] = useState("Ожидание нажатия клавиши");
|
|
201
|
+
|
|
202
|
+
const keyDownListeners = [
|
|
203
|
+
{ eventKeyCode: EVENT_KEY_CODES.ESCAPE, onMatch: () => setResult("Отменено по Esc") },
|
|
204
|
+
...(arrowsEnabled
|
|
205
|
+
? [
|
|
206
|
+
{
|
|
207
|
+
eventKeyCode: [EVENT_KEY_CODES.ARROW_LEFT, EVENT_KEY_CODES.ARROW_RIGHT],
|
|
208
|
+
onMatch: (event: KeyboardEvent) =>
|
|
209
|
+
setResult(event.keyCode === EVENT_KEY_CODES.ARROW_LEFT ? "Стрелка влево" : "Стрелка вправо"),
|
|
210
|
+
},
|
|
211
|
+
]
|
|
212
|
+
: []),
|
|
213
|
+
];
|
|
214
|
+
|
|
215
|
+
return (
|
|
216
|
+
<ComposedKeyDownListener keyDownListeners={keyDownListeners}>
|
|
217
|
+
<div style={{ display: "flex", flexDirection: "column", gap: "16px", maxWidth: "400px" }}>
|
|
218
|
+
<div>Набор слушателей меняется на лету — содержимое при этом не перемонтируется.</div>
|
|
219
|
+
<input type="text" placeholder="Введите текст" style={{ padding: "8px", width: "100%" }} />
|
|
220
|
+
<label style={{ display: "flex", alignItems: "center", gap: "8px" }}>
|
|
221
|
+
<input
|
|
222
|
+
type="checkbox"
|
|
223
|
+
checked={arrowsEnabled}
|
|
224
|
+
onChange={(event) => setArrowsEnabled(event.target.checked)}
|
|
225
|
+
/>
|
|
226
|
+
Добавить слушатель стрелок — введённый текст и фокус сохранятся
|
|
227
|
+
</label>
|
|
228
|
+
<div style={{ border: "1px dashed rgb(125, 131, 138)", borderRadius: "4px", padding: "16px" }}>
|
|
229
|
+
{result}
|
|
230
|
+
</div>
|
|
231
|
+
</div>
|
|
232
|
+
</ComposedKeyDownListener>
|
|
233
|
+
);
|
|
234
|
+
};
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### Example
|
|
238
|
+
|
|
239
|
+
`stories/ComposedKeyDownListener/examples/Example.tsx`
|
|
240
|
+
|
|
241
|
+
```tsx
|
|
242
|
+
import React, { useRef, useState } from "react";
|
|
243
|
+
import {
|
|
244
|
+
Button,
|
|
245
|
+
ComposedKeyDownListener,
|
|
246
|
+
EButtonTheme,
|
|
247
|
+
EComponentSize,
|
|
248
|
+
EVENT_KEY_CODES,
|
|
249
|
+
} from "@sberbusiness/triplex-next";
|
|
250
|
+
|
|
251
|
+
export const Example = () => {
|
|
252
|
+
const [opened, setOpened] = useState(true);
|
|
253
|
+
const [status, setStatus] = useState("Черновик не сохранён");
|
|
254
|
+
const panelRef = useRef<HTMLDivElement>(null);
|
|
255
|
+
|
|
256
|
+
const handleSave = () => {
|
|
257
|
+
setStatus("Черновик сохранён");
|
|
258
|
+
setOpened(false);
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
const handleCancel = () => {
|
|
262
|
+
setStatus("Сохранение отменено");
|
|
263
|
+
setOpened(false);
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
// Слушатели глобальные, поэтому Enter на сфокусированной кнопке панели дошёл бы
|
|
267
|
+
// и до onMatch, и до её onClick. Отфильтровываем такие события по event.target —
|
|
268
|
+
// действие кнопки должно выигрывать у горячей клавиши.
|
|
269
|
+
const handleSaveOnEnter = (event: KeyboardEvent) => {
|
|
270
|
+
if (event.target instanceof HTMLButtonElement && panelRef.current?.contains(event.target)) {
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
handleSave();
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
// Слушатели монтируются только вместе с панелью, поэтому горячие клавиши
|
|
278
|
+
// работают лишь пока она открыта.
|
|
279
|
+
const keyDownListeners = [
|
|
280
|
+
{ eventKeyCode: EVENT_KEY_CODES.ENTER, onMatch: handleSaveOnEnter },
|
|
281
|
+
{ eventKeyCode: EVENT_KEY_CODES.ESCAPE, onMatch: handleCancel },
|
|
282
|
+
];
|
|
283
|
+
|
|
284
|
+
if (!opened) {
|
|
285
|
+
return (
|
|
286
|
+
<div style={{ display: "flex", flexDirection: "column", gap: "16px", maxWidth: "400px" }}>
|
|
287
|
+
<div>{status}</div>
|
|
288
|
+
<Button theme={EButtonTheme.SECONDARY} size={EComponentSize.MD} onClick={() => setOpened(true)}>
|
|
289
|
+
Открыть панель снова
|
|
290
|
+
</Button>
|
|
291
|
+
</div>
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return (
|
|
296
|
+
<ComposedKeyDownListener keyDownListeners={keyDownListeners}>
|
|
297
|
+
<div
|
|
298
|
+
ref={panelRef}
|
|
299
|
+
style={{
|
|
300
|
+
display: "flex",
|
|
301
|
+
flexDirection: "column",
|
|
302
|
+
gap: "16px",
|
|
303
|
+
maxWidth: "400px",
|
|
304
|
+
border: "1px solid rgb(125, 131, 138)",
|
|
305
|
+
borderRadius: "8px",
|
|
306
|
+
padding: "16px",
|
|
307
|
+
}}
|
|
308
|
+
>
|
|
309
|
+
<div>Сохранить черновик? Enter — сохранить, Esc — отменить.</div>
|
|
310
|
+
<div style={{ display: "flex", gap: "8px" }}>
|
|
311
|
+
<Button theme={EButtonTheme.GENERAL} size={EComponentSize.MD} onClick={handleSave}>
|
|
312
|
+
Сохранить
|
|
313
|
+
</Button>
|
|
314
|
+
<Button theme={EButtonTheme.SECONDARY} size={EComponentSize.MD} onClick={handleCancel}>
|
|
315
|
+
Отменить
|
|
316
|
+
</Button>
|
|
317
|
+
</div>
|
|
318
|
+
</div>
|
|
319
|
+
</ComposedKeyDownListener>
|
|
320
|
+
);
|
|
321
|
+
};
|
|
322
|
+
```
|
|
323
|
+
|
|
@@ -39,7 +39,7 @@ version: "1.0"
|
|
|
39
39
|
|
|
40
40
|
| Prop | Тип | По умолчанию | Описание |
|
|
41
41
|
|---|---|---|---|
|
|
42
|
-
| `sticky` | `boolean` | `false` | Футер прилипает к нижней границе при
|
|
42
|
+
| `sticky` | `boolean` | `false` | Футер прилипает к нижней границе при скролле, если ширина viewport от 992px, а высота больше 800px. **Только для `type=FIRST`**; для `type=SECOND` типизирован как `never`. |
|
|
43
43
|
| `size` | `EComponentSize` | — | Размер острова (Island). **Доступен только для `type=FIRST`**; для `type=SECOND` типизирован как `never`. |
|
|
44
44
|
|
|
45
45
|
### Ограничения по типам
|
|
@@ -111,6 +111,7 @@ version: "1.0"
|
|
|
111
111
|
|
|
112
112
|
| Дата | Изменение |
|
|
113
113
|
|---|---|
|
|
114
|
+
| 2026-08-06 | Sticky-позиционирование доступно вне `LightBox` при ширине viewport от 992px и отключено при высоте viewport не больше 800px. |
|
|
114
115
|
| 2026-06-19 | Создан документ. AI-рефакторинг (clsx order, JSDoc на props, displayName), unit-тесты, миграция stories на modern pattern. |
|
|
115
116
|
|
|
116
117
|
---
|