@sberbusiness/triplex-next-mcp-server 0.36.0-beta0 → 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.
Files changed (46) hide show
  1. package/bundle/components/AlertContext.md +268 -0
  2. package/bundle/components/Button.md +5 -2
  3. package/bundle/components/ButtonDropdown.md +9 -1
  4. package/bundle/components/ButtonDropdownExtended.md +422 -0
  5. package/bundle/components/ButtonIcon.md +7 -1
  6. package/bundle/components/Calendar.md +183 -368
  7. package/bundle/components/Caption.md +291 -0
  8. package/bundle/components/Chip.md +310 -314
  9. package/bundle/components/CodeText.md +197 -0
  10. package/bundle/components/Col.md +311 -162
  11. package/bundle/components/CollapsibleTree.md +6 -3
  12. package/bundle/components/CollapsibleTreeExtended.md +6 -2
  13. package/bundle/components/Dropdown.md +748 -346
  14. package/bundle/components/FormField.md +468 -232
  15. package/bundle/components/IconWrapper.md +142 -89
  16. package/bundle/components/Island.md +221 -129
  17. package/bundle/components/KeyDownListener.md +215 -102
  18. package/bundle/components/LightBox.md +2252 -233
  19. package/bundle/components/List.md +26 -1
  20. package/bundle/components/ListActionItem.md +157 -0
  21. package/bundle/components/ListItem.md +34 -9
  22. package/bundle/components/ListItemSelectable.md +2 -2
  23. package/bundle/components/ListMaster.md +4 -5
  24. package/bundle/components/ListTableItem.md +391 -0
  25. package/bundle/components/LoaderMiddle.md +265 -0
  26. package/bundle/components/LoaderScreen.md +178 -81
  27. package/bundle/components/Notification.md +21 -21
  28. package/bundle/components/Page.md +1 -0
  29. package/bundle/components/Portal.md +131 -67
  30. package/bundle/components/Row.md +171 -69
  31. package/bundle/components/Text.md +320 -0
  32. package/bundle/components/TextField.md +364 -97
  33. package/bundle/components/ThemeProvider.md +305 -117
  34. package/bundle/components/Title.md +296 -0
  35. package/bundle/components/Tooltip.md +421 -181
  36. package/bundle/guides/commits.md +126 -24
  37. package/bundle/guides/context.md +1 -1
  38. package/bundle/guides/tests.md +42 -0
  39. package/bundle/manifest.json +109 -23
  40. package/bundle/release-notes/1.37.0.mdx +42 -1
  41. package/bundle/release-notes/1.38.0.mdx +36 -0
  42. package/bundle/release-notes/1.39.0.mdx +66 -0
  43. package/bundle/release-notes/1.40.0.mdx +27 -0
  44. package/bundle/release-notes/1.41.0.mdx +83 -0
  45. package/bundle/release-notes/1.42.0.mdx +7 -0
  46. package/package.json +1 -1
@@ -1,11 +1,9 @@
1
1
  ---
2
2
  component: KeyDownListener
3
- category: Utilities
4
- figma-node: ""
5
- figma-file: ""
6
- related: [ComposedKeyDownListener, WindowResizeListener]
3
+ category: KeyDownListener
4
+ related: [ComposedKeyDownListener, TriggerClickOnKeyDownEvent, SelectExtendedField, SliderExtended]
7
5
  tokens: []
8
- stories: ""
6
+ stories: stories/KeyDownListener/KeyDownListener.stories.tsx
9
7
  version: "1.0"
10
8
  ---
11
9
 
@@ -13,145 +11,260 @@ version: "1.0"
13
11
 
14
12
  ## Назначение
15
13
 
16
- Невизуальный компонент-слушатель `keydown` на `window`. При совпадении
17
- кода нажатой клавиши с `eventKeyCode` (одно значение или массив) вызывает
18
- `onMatch(event)`. Рендерит `children` как есть (или `null`) — не добавляет
19
- обёртку в DOM.
14
+ Сервисный headless-компонент. Пока смонтирован, слушает `keydown` на `window` и
15
+ вызывает `onMatch`, если числовой код нажатой клавиши (`event.keyCode`) совпал с
16
+ `eventKeyCode`. Собственной разметки не рендерит: `children` выводятся как есть,
17
+ без обёртки; без `children` не рендерит ничего.
20
18
 
21
- Используй когда: нужно отреагировать на глобальное нажатие клавиш
22
- (Escape для закрытия модалки, Tab/ArrowKey для навигации в кастомных
23
- виджетах), но не привязываться к конкретному focus-target'у.
19
+ Используй когда: нужна глобальная (документная) горячая клавиша, работающая
20
+ независимо от того, где находится фокус — закрытие оверлея по `Esc`, стрелки для
21
+ управления слайдером/ползунком, подтверждение по `Enter`.
24
22
 
25
- Для нескольких параллельных слушателей используй `ComposedKeyDownListener` —
26
- принимает массив конфигов и композитит их в иерархию.
27
-
28
- ---
29
-
30
- ## Файловая структура
31
-
32
- ```text
33
- src/components/KeyDownListener/
34
- ├── KeyDownListener.tsx # React.Component (class) с window-keydown
35
- ├── ComposedKeyDownListener.tsx # FC, сворачивает массив listener'ов в tree
36
- └── index.ts # Barrel: оба компонента
37
- ```
38
-
39
- Нет styles, stories, tests.
23
+ Не используй когда:
24
+ - Клавиша должна обрабатываться только внутри конкретного элемента — навесь
25
+ `onKeyDown` на сам элемент, иначе обработчик сработает при вводе в другое поле
26
+ на странице.
27
+ - Нужно кликнуть по конкретной кнопке по нажатию клавиши — есть готовый
28
+ `TriggerClickOnKeyDownEvent`.
29
+ - Нужно несколько разных клавиш с разными обработчиками — используй
30
+ `ComposedKeyDownListener` (см. «Связанные компоненты»).
40
31
 
41
32
  ---
42
33
 
43
34
  ## Варианты и props
44
35
 
45
- ### KeyDownListener — `IKeyDownListenerProps`
36
+ ### Обязательные props
46
37
 
47
- | Prop | Тип | Обязателен | Описание |
48
- |---|---|---|---|
49
- | `eventKeyCode` | `number \| number[]` | Да | Код клавиши (число — это `KeyboardEvent.keyCode`, не `key`). Один код или массив. |
50
- | `onMatch` | `(event: KeyboardEvent) => void` | Да | Вызывается при совпадении кода. |
51
- | `children` | `React.ReactNode` | Нет | Рендерятся "прозрачно" (компонент не оборачивает в DOM). |
52
-
53
- Тип кода: `keyCode = (typeof EVENT_KEY_CODES)[keyof typeof EVENT_KEY_CODES]`
54
- (числа из `@sberbusiness/triplex-next/utils/keyboard`): `ENTER=13`, `ESCAPE=27`,
55
- `TAB=9`, `ARROW_*=37-40`, и т.д.
38
+ | Prop | Тип | Описание |
39
+ |---|---|---|
40
+ | `eventKeyCode` | `number \| number[]` | Код клавиши из `EVENT_KEY_CODES` или массив кодов. Массив — «любая из перечисленных клавиш»; различить нажатую можно по `event.keyCode` внутри `onMatch` |
41
+ | `onMatch` | `(event: KeyboardEvent) => void` | Вызывается при совпадении. Получает нативный `KeyboardEvent` (не React SyntheticEvent) |
56
42
 
57
- ### ComposedKeyDownListener — `IKeyDownListenerManagerProps`
43
+ ### Опциональные props
58
44
 
59
- | Prop | Тип | Обязателен | Описание |
45
+ | Prop | Тип | По умолчанию | Описание |
60
46
  |---|---|---|---|
61
- | `keyDownListeners` | `IKeyDownListenerProps[]` | Да | Массив конфигов. Для каждого создаётся свой `KeyDownListener`. |
62
- | `children` | `React.ReactNode` | Нет | Обёрнуты всеми listener'ами в иерархию (последний в массиве — самый внешний). |
47
+ | `children` | `React.ReactNode` | — | Рендерится как есть. Компонент не оборачивает содержимое и никак на него не влияет — вложение нужно только для читаемости кода |
48
+
49
+ ### Особенности поведения
50
+
51
+ - Слушатель навешивается на `window` в `componentDidMount` и снимается в
52
+ `componentWillUnmount`. Компонент не вызывает `preventDefault` и не
53
+ останавливает всплытие — это ответственность `onMatch`.
54
+ - Сравнение идёт по устаревшему `event.keyCode`, а не по `event.key`. Коды
55
+ берутся из `EVENT_KEY_CODES` (`@sberbusiness/triplex-next`); не хардкодь числа.
56
+ - `onMatch` и `eventKeyCode` читаются из `this.props` в момент события, поэтому
57
+ свежие значения после rerender применяются без переподписки.
58
+ - Несколько смонтированных `KeyDownListener` на одну клавишу срабатывают все —
59
+ порядок соответствует порядку монтирования.
60
+ - Компонент не проверяет, где находится фокус: `Esc` сработает и во время ввода
61
+ в поле. Если это нежелательно, фильтруй в `onMatch` по `event.target`.
63
62
 
64
63
  ---
65
64
 
66
- ## Ключевые особенности реализации
67
-
68
- ### `KeyDownListener` — class component
65
+ ## Дизайн-токены
69
66
 
70
- ```tsx
71
- export class KeyDownListener extends React.Component<IKeyDownListenerProps> {
72
- public componentDidMount() { window.addEventListener("keydown", this.handleKeyDown); }
73
- public componentWillUnmount() { window.removeEventListener("keydown", this.handleKeyDown); }
74
- public render() { return this.props.children || null; }
75
- }
76
- ```
67
+ Собственных стилей и CSS-переменных нет — компонент не рендерит разметку.
77
68
 
78
- - **Сохранён как class**, хотя весь остальной код библиотеки — функциональные компоненты. Миграция на `useEffect` возможна, но class сохраняет идентичность listener'а через reference handler'а — аналог можно сделать через `useEffect` + стабильный handler (через ref на `onMatch`).
79
- - Не `forwardRef` — компонент не имеет своего DOM.
69
+ ---
80
70
 
81
- ### Использование `keyCode`, а не `key` / `code`
71
+ ## Инварианты
82
72
 
83
- ```ts
84
- if (typeof eventKeyCode === "number") {
85
- eventKeyCode === event.keyCode && onMatch(event);
86
- } else if (eventKeyCode.includes(event.keyCode)) {
87
- onMatch(event);
88
- }
89
- ```
73
+ - **`KeyDownListener` — class-компонент**, и это часть публичного контракта:
74
+ `ref` указывает на инстанс класса, метод `handleKeyDown` публичный. Не
75
+ переписывать на функциональный компонент.
76
+ - **Без `forwardRef` — осознанное исключение** из общего правила библиотеки:
77
+ компонент не рендерит собственный host-элемент, форвардить ref некуда.
78
+ - **Имена props `children`, `eventKeyCode`, `onMatch`** и интерфейс
79
+ `IKeyDownListenerProps` (экспортируется из barrel) — публичное API, не менять.
80
+ `IKeyDownListenerProps` переиспользуется в `TriggerClickOnKeyDownEvent`
81
+ (`Pick<IKeyDownListenerProps, "eventKeyCode">`) и в props
82
+ `ComposedKeyDownListener`.
83
+ - **Сравнение по `event.keyCode`** — не мигрировать на `event.key` без отдельной
84
+ задачи: у `EVENT_KEYS.SPACE` значения `["Spacebar", "Space"]` не совпадают с
85
+ современным `event.key === " "`, и миграция изменит наблюдаемое поведение у
86
+ потребителей.
87
+ - **`render` возвращает `this.props.children || null`** — falsy-`children`
88
+ (`0`, `""`) осознанно дают `null`. Не заменять на `??`.
89
+ - Реализация остаётся React 17-совместимой (класс, без хуков и React 18-only
90
+ API): код синхронизируется в `release-0`.
90
91
 
91
- - `event.keyCode` — **deprecated** в современном вебе (замена: `event.key` или `event.code`). Компонент и набор `EVENT_KEY_CODES` сохранили legacy-подход.
92
- - Числовые константы (13, 27, 40, ...) должны использоваться через `EVENT_KEY_CODES.ENTER` / `.ESCAPE` и т.д., не литерально.
93
- - Для новых компонентов предпочитать `isKey(event.key, "ENTER")` из `utils/keyboard` — она поддерживает `key`-strings и legacy-fallback.
92
+ ---
94
93
 
95
- ### `onMatch` не кешируется в class state
94
+ ## Accessibility
96
95
 
97
- Каждый keydown вытаскивает `this.props.onMatch` актуальный — замыкание не устаревает. При миграции на hooks это поведение нужно воспроизвести через ref.
96
+ - Собственной ARIA-семантики нет — компонент прозрачен для accessibility-дерева
97
+ и не рендерит фокусируемых элементов.
98
+ - Горячая клавиша глобальная: она перехватывается независимо от позиции фокуса и
99
+ не анонсируется screen reader'ом. Если клавиша дублирует видимое действие,
100
+ оставь пользователю и обычный путь (кнопку) — клавиатурный шорткат не должен
101
+ быть единственным способом выполнить действие.
102
+ - `Esc` для закрытия оверлеев — ожидаемое поведение; возврат фокуса на триггер
103
+ после закрытия компонент не делает, это ответственность потребителя.
98
104
 
99
- ### `ComposedKeyDownListener` — reduce-вариант через цикл
105
+ ---
100
106
 
101
- ```tsx
102
- let accumulatedElement = children as JSX.Element;
103
- for (let i = 0; i < keyDownListeners.length; i++) {
104
- accumulatedElement = <KeyDownListener {...}>{accumulatedElement}</KeyDownListener>;
105
- }
106
- ```
107
+ ## Связанные компоненты
107
108
 
108
- - Последний listener в массиве → самый внешний wrapper в tree.
109
- - Порядок не влияет на результат (все listener'ы слушают `window` независимо), но Reactу важен для reconciliation — не менять порядок без нужды.
110
- - **Внимание:** ключи не проставляются. При рендере одного и того же конфига это ок, но при динамическом изменении массива React может ломать состояние child'ов — используй stable ordering.
109
+ ### `ComposedKeyDownListener` (`src/components/KeyDownListener/ComposedKeyDownListener.tsx`)
110
+
111
+ Композитор: оборачивает `children` в `KeyDownListener` по одному на каждый
112
+ элемент `keyDownListeners`. Экспортируется из того же barrel вместе со своим
113
+ интерфейсом props `IComposedKeyDownListenerProps` — публичное API, не менять.
114
+
115
+ Собственного AI.md пока не имеет — вся его логика описана здесь. По правилу
116
+ `docs/ai/CONTEXT.md` § «Когда создавать `{ComponentName}-ai.md`» он его
117
+ заслуживает (оба условия выполнены: barrel-экспорт + собственный prop
118
+ `keyDownListeners`), но по scope задачи TRI-48 соседние экспортируемые
119
+ компоненты выносятся в отдельные задачи. Компонент добавлен отдельной строкой в
120
+ `docs/ai/ROADMAP.md`, документ будет заведён своей задачей. Следствие, о котором
121
+ нужно знать до тех пор: `scripts/generateMcpData.ts` собирает записи по файлам
122
+ `src/components/*/*-ai.md`, поэтому отдельной записи в `mcp-data.json` у
123
+ `ComposedKeyDownListener` нет — агент найдёт его только через этот документ.
124
+
125
+ | Prop | Тип | Описание |
126
+ |---|---|---|
127
+ | `keyDownListeners` | `IKeyDownListenerProps[]` | Конфигурация слушателей. Используются только `eventKeyCode` и `onMatch`, поле `children` каждого элемента игнорируется |
128
+ | `children` | `React.ReactNode` | Содержимое. Рендерится как есть; при пустом `keyDownListeners` — просто `children` |
129
+
130
+ Первый элемент массива оказывается самым внутренним слушателем. На порядок
131
+ срабатывания это влияет слабо — все слушатели подписаны на `window` независимо
132
+ друг от друга, а порядок вызова совпадает с порядком элементов массива.
133
+
134
+ **Ограничение: длина `keyDownListeners` должна быть стабильной между рендерами.**
135
+ Глубина вложенности равна длине массива, поэтому при её изменении на позиции,
136
+ где раньше были `children`, React монтирует новый `KeyDownListener`, а сами
137
+ `children` уезжают на уровень ниже и перемонтируются: DOM-узлы пересоздаются,
138
+ введённые значения инпутов, фокус и текущие анимации теряются. Если набор
139
+ слушателей динамический — держи массив постоянной длины (например, с
140
+ `onMatch`-заглушками) или используй `KeyDownListener` напрямую.
141
+
142
+ ### Потребители внутри библиотеки
143
+
144
+ - `TriggerClickOnKeyDownEvent` (`src/components/Triggers/`) — по нажатию клавиши
145
+ вызывает `click` на элементе из `targetRef`.
146
+ - `SelectExtendedField` — закрывает выпадающий список по `Esc`.
147
+ - `SliderExtendedDot`, `SliderExtendedTrack` — стрелки для перемещения ползунка,
148
+ слушатели монтируются только когда элемент в фокусе.
111
149
 
112
150
  ---
113
151
 
114
- ## Инварианты
152
+ ## Stories
153
+
154
+ Основные истории: `stories/KeyDownListener/KeyDownListener.stories.tsx`
155
+ Файлы примеров: `stories/KeyDownListener/examples/`
115
156
 
116
- - **`eventKeyCode` — `number | number[]`** (не `string`/`KeyboardEvent["key"]`). Публичный контракт. При смене на `key`-строки — breaking change.
117
- - **`onMatch(event: KeyboardEvent)`** — получает нативное событие, не синтетическое React-событие. Не менять тип на `React.KeyboardEvent`.
118
- - **Class component** — сохранить при рефакторинге; миграция на FC допустима, но должна воспроизвести `componentWillUnmount`-очистку и актуальный `onMatch`.
119
- - **`window.addEventListener`** — listener на окне, не на document. Не менять без согласования (влияет на порядок обработки событий с портальными элементами).
120
- - **`children` рендерятся без обёртки** — компонент "прозрачный" в DOM. Не добавлять `<div>`/`<Fragment>` без нужды.
121
- - **`ComposedKeyDownListener`** — простой композитор. Не усложнять (например, не добавлять `useMemo`/контексты) без реальной нужды.
157
+ | Story | Example file | Что демонстрирует |
158
+ |---|---|---|
159
+ | `Playground` | `Playground.tsx` | Интерактивный выбор `eventKeyCode` и счётчик срабатываний `onMatch` |
160
+ | `Default` | `Default.tsx` | Слушатель одной клавиши: панель скрывается по `Esc` |
161
+ | `WithMultipleKeys` | `WithMultipleKeys.tsx` | Массив кодов в `eventKeyCode`; нажатая клавиша различается по `event.keyCode` |
162
+ | `Example` | `Example.tsx` | `ComposedKeyDownListener`: `Enter` подтверждает, `Esc` отменяет |
163
+ | `VisualTests` | — | Состояние после срабатывания слушателя: `play` диспатчит на `window` `keydown` с `keyCode` клавиши `Esc` |
122
164
 
123
165
  ---
124
166
 
125
- ## Accessibility
167
+ ## История изменений
126
168
 
127
- - Компонент работает на уровне окна, перехватывает keydown до того, как фокусируемый элемент получит шанс обработать — при использовании **в составе интерактивных виджетов** учитывай, что нативный `onKeyDown` на inputе/кнопке сработает **после** глобального listener'а (из-за bubbling), но глобальный listener активен всегда, даже если фокус в другом месте.
128
- - Не подменяй focus management'ом. Для Escape в модалках предпочитай `focus trap` + `onKeyDown` на overlay'е.
129
- - Listener активен даже в скрытом state компонента-родителя, если сам KeyDownListener не размонтирован. Контролируй монтирование через условный рендеринг.
169
+ | Дата | Изменение |
170
+ |---|---|
171
+ | 2026-08-04 | По ревью PR #502: интерфейс props `IComposedKeyDownListenerProps` стал экспортируемым — публичная поверхность пакета расширена аддитивно. `ComposedKeyDownListener` добавлен отдельной строкой в `docs/ai/ROADMAP.md`, собственный AI.md вынесен в отдельную задачу. |
172
+ | 2026-08-04 | Создан документ. AI-рефакторинг (TRI-48): JSDoc на props `IKeyDownListenerProps` и `ComposedKeyDownListener`, ветвление в `handleKeyDown` сведено к одной проверке (снят `eslint-disable`), в `ComposedKeyDownListener` убран `as JSX.Element` и добавлен `displayName`, unit-тесты (16 кейсов на оба компонента), stories по modern pattern. Публичный API не менялся; компонент остаётся class-компонентом, `forwardRef` осознанно отсутствует. |
130
173
 
131
174
  ---
132
175
 
133
- ## Дизайн-токены
176
+ ## Примеры использования
134
177
 
135
- Нет.
178
+ ### Default
136
179
 
137
- ---
180
+ `stories/KeyDownListener/examples/Default.tsx`
138
181
 
139
- ## Связанные компоненты
182
+ ```tsx
183
+ import React, { useState } from "react";
184
+ import { KeyDownListener, EVENT_KEY_CODES } from "@sberbusiness/triplex-next";
185
+
186
+ export const Default = () => {
187
+ const [visible, setVisible] = useState(true);
188
+
189
+ return (
190
+ <div style={{ display: "flex", flexDirection: "column", gap: "16px", maxWidth: "400px" }}>
191
+ <KeyDownListener eventKeyCode={EVENT_KEY_CODES.ESCAPE} onMatch={() => setVisible(false)} />
192
+ <div>Нажмите Esc, чтобы скрыть панель.</div>
193
+ {visible && (
194
+ <div style={{ border: "1px dashed rgb(125, 131, 138)", borderRadius: "4px", padding: "16px" }}>
195
+ Панель скрывается по нажатию Esc в любом месте страницы.
196
+ </div>
197
+ )}
198
+ </div>
199
+ );
200
+ };
201
+ ```
140
202
 
141
- - `ComposedKeyDownListener` — обёртка для нескольких listener'ов.
142
- - `WindowResizeListener` — аналогичный паттерн для resize.
143
- - `@sberbusiness/triplex-next/utils/keyboard` — `EVENT_KEY_CODES`, `EVENT_KEYS`, `isKey`.
203
+ ### WithMultipleKeys
144
204
 
145
- ---
205
+ `stories/KeyDownListener/examples/WithMultipleKeys.tsx`
146
206
 
147
- ## Stories
207
+ ```tsx
208
+ import React, { useState } from "react";
209
+ import { KeyDownListener, EVENT_KEY_CODES } from "@sberbusiness/triplex-next";
210
+
211
+ export const WithMultipleKeys = () => {
212
+ const [value, setValue] = useState(0);
213
+
214
+ const handleMatch = (event: KeyboardEvent) => {
215
+ setValue((prevValue) => (event.keyCode === EVENT_KEY_CODES.ARROW_LEFT ? prevValue - 1 : prevValue + 1));
216
+ };
217
+
218
+ return (
219
+ <KeyDownListener eventKeyCode={[EVENT_KEY_CODES.ARROW_LEFT, EVENT_KEY_CODES.ARROW_RIGHT]} onMatch={handleMatch}>
220
+ <div style={{ display: "flex", flexDirection: "column", gap: "16px", maxWidth: "400px" }}>
221
+ <div>Один слушатель реагирует на несколько клавиш: стрелка влево и стрелка вправо.</div>
222
+ <div style={{ border: "1px dashed rgb(125, 131, 138)", borderRadius: "4px", padding: "16px" }}>
223
+ Значение: {value}
224
+ </div>
225
+ </div>
226
+ </KeyDownListener>
227
+ );
228
+ };
229
+ ```
148
230
 
149
- Нет.
231
+ ### Example
150
232
 
151
- ---
233
+ `stories/KeyDownListener/examples/Example.tsx`
152
234
 
153
- ## История изменений
235
+ ```tsx
236
+ import React, { useState } from "react";
237
+ import { ComposedKeyDownListener, EVENT_KEY_CODES } from "@sberbusiness/triplex-next";
238
+
239
+ export const Example = () => {
240
+ const [result, setResult] = useState<string | null>(null);
241
+
242
+ const keyDownListeners = [
243
+ { eventKeyCode: EVENT_KEY_CODES.ENTER, onMatch: () => setResult("Подтверждено по Enter") },
244
+ { eventKeyCode: EVENT_KEY_CODES.ESCAPE, onMatch: () => setResult("Отменено по Esc") },
245
+ ];
246
+
247
+ if (result !== null) {
248
+ return (
249
+ <div style={{ display: "flex", flexDirection: "column", gap: "16px", maxWidth: "400px" }}>
250
+ <div>{result}</div>
251
+ <button type="button" onClick={() => setResult(null)}>
252
+ Показать панель снова
253
+ </button>
254
+ </div>
255
+ );
256
+ }
257
+
258
+ return (
259
+ <ComposedKeyDownListener keyDownListeners={keyDownListeners}>
260
+ <div style={{ display: "flex", flexDirection: "column", gap: "16px", maxWidth: "400px" }}>
261
+ <div>Панель с горячими клавишами: Enter подтверждает, Esc отменяет.</div>
262
+ <div style={{ border: "1px dashed rgb(125, 131, 138)", borderRadius: "4px", padding: "16px" }}>
263
+ Ожидание нажатия клавиши.
264
+ </div>
265
+ </div>
266
+ </ComposedKeyDownListener>
267
+ );
268
+ };
269
+ ```
154
270
 
155
- | Дата | Изменение |
156
- |---|---|
157
- | 2026-04-18 | Создан документ |