@sberbusiness/triplex-next-mcp-server 0.24.0-beta1

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 (140) hide show
  1. package/README.md +124 -0
  2. package/bundle/.gitkeep +0 -0
  3. package/bundle/components/AbstractTree.md +198 -0
  4. package/bundle/components/AccordionBase.md +227 -0
  5. package/bundle/components/Alert.md +317 -0
  6. package/bundle/components/Amount.md +225 -0
  7. package/bundle/components/AmountField.md +289 -0
  8. package/bundle/components/Avatar.md +209 -0
  9. package/bundle/components/Badge.md +277 -0
  10. package/bundle/components/Body.md +167 -0
  11. package/bundle/components/Button.md +230 -0
  12. package/bundle/components/ButtonDropdown.md +159 -0
  13. package/bundle/components/ButtonIcon.md +122 -0
  14. package/bundle/components/Calendar.md +445 -0
  15. package/bundle/components/Card.md +374 -0
  16. package/bundle/components/CarouselExtended.md +250 -0
  17. package/bundle/components/Checkbox.md +126 -0
  18. package/bundle/components/CheckboxTree.md +280 -0
  19. package/bundle/components/CheckboxTreeExtended.md +321 -0
  20. package/bundle/components/CheckboxXGroup.md +68 -0
  21. package/bundle/components/CheckboxYGroup.md +67 -0
  22. package/bundle/components/Chip.md +404 -0
  23. package/bundle/components/ChipGroup.md +179 -0
  24. package/bundle/components/Col.md +221 -0
  25. package/bundle/components/CollapsableTree.md +255 -0
  26. package/bundle/components/Confirm.md +343 -0
  27. package/bundle/components/DateField.md +371 -0
  28. package/bundle/components/DatePickerExtended.md +342 -0
  29. package/bundle/components/DateRange.md +275 -0
  30. package/bundle/components/DesignTokens.md +385 -0
  31. package/bundle/components/Divider.md +196 -0
  32. package/bundle/components/DocumentNumberEdit.md +280 -0
  33. package/bundle/components/Dropdown.md +433 -0
  34. package/bundle/components/Ellipsis.md +208 -0
  35. package/bundle/components/EmptyView.md +99 -0
  36. package/bundle/components/ExpandAnimation.md +281 -0
  37. package/bundle/components/Footer.md +339 -0
  38. package/bundle/components/FormField.md +326 -0
  39. package/bundle/components/FormGroup.md +99 -0
  40. package/bundle/components/Gap.md +113 -0
  41. package/bundle/components/Header.md +280 -0
  42. package/bundle/components/HelpBox.md +154 -0
  43. package/bundle/components/IconWrapper.md +145 -0
  44. package/bundle/components/Island.md +197 -0
  45. package/bundle/components/IslandAccordion.md +230 -0
  46. package/bundle/components/IslandWidget.md +233 -0
  47. package/bundle/components/KeyDownListener.md +157 -0
  48. package/bundle/components/LightBox.md +322 -0
  49. package/bundle/components/Link.md +161 -0
  50. package/bundle/components/List.md +247 -0
  51. package/bundle/components/ListMaster.md +204 -0
  52. package/bundle/components/Loader.md +161 -0
  53. package/bundle/components/LoaderScreen.md +134 -0
  54. package/bundle/components/Marker.md +127 -0
  55. package/bundle/components/MarkerStatus.md +148 -0
  56. package/bundle/components/MediaWidth.md +169 -0
  57. package/bundle/components/MobileView.md +120 -0
  58. package/bundle/components/ModalWindow.md +308 -0
  59. package/bundle/components/MonthYearField.md +168 -0
  60. package/bundle/components/MultiselectField.md +213 -0
  61. package/bundle/components/Notification.md +233 -0
  62. package/bundle/components/NumberField.md +178 -0
  63. package/bundle/components/OrderedList.md +169 -0
  64. package/bundle/components/Overlay.md +237 -0
  65. package/bundle/components/Page.md +246 -0
  66. package/bundle/components/Pagination.md +298 -0
  67. package/bundle/components/Portal.md +119 -0
  68. package/bundle/components/Radio.md +174 -0
  69. package/bundle/components/Row.md +125 -0
  70. package/bundle/components/SMSField.md +160 -0
  71. package/bundle/components/SegmentedControl.md +235 -0
  72. package/bundle/components/SelectExtendedField.md +322 -0
  73. package/bundle/components/SelectField.md +213 -0
  74. package/bundle/components/Skeleton.md +129 -0
  75. package/bundle/components/Slider.md +157 -0
  76. package/bundle/components/SliderExtended.md +210 -0
  77. package/bundle/components/SliderRange.md +160 -0
  78. package/bundle/components/SmallInput.md +101 -0
  79. package/bundle/components/Spoiler.md +121 -0
  80. package/bundle/components/StatusTracker.md +151 -0
  81. package/bundle/components/Step.md +121 -0
  82. package/bundle/components/Stepper.md +163 -0
  83. package/bundle/components/Suggest.md +160 -0
  84. package/bundle/components/SuggestField.md +157 -0
  85. package/bundle/components/SwipeableArea.md +144 -0
  86. package/bundle/components/Table.md +199 -0
  87. package/bundle/components/Tabs.md +152 -0
  88. package/bundle/components/TabsExtended.md +273 -0
  89. package/bundle/components/TabsLine.md +189 -0
  90. package/bundle/components/Tag.md +135 -0
  91. package/bundle/components/TagColor.md +121 -0
  92. package/bundle/components/TagGroup.md +100 -0
  93. package/bundle/components/TextField.md +152 -0
  94. package/bundle/components/TextareaField.md +124 -0
  95. package/bundle/components/ThemeProvider.md +181 -0
  96. package/bundle/components/Tooltip.md +254 -0
  97. package/bundle/components/TopOverlay.md +145 -0
  98. package/bundle/components/TreeView.md +224 -0
  99. package/bundle/components/Triggers.md +121 -0
  100. package/bundle/guides/codestyle.md +205 -0
  101. package/bundle/guides/commits.md +166 -0
  102. package/bundle/guides/context.md +277 -0
  103. package/bundle/guides/stories.md +618 -0
  104. package/bundle/guides/template.md +154 -0
  105. package/bundle/guides/tests.md +301 -0
  106. package/bundle/icons.json +4156 -0
  107. package/bundle/manifest.json +796 -0
  108. package/bundle/release-notes/1.1.0.mdx +26 -0
  109. package/bundle/release-notes/1.1.1.mdx +14 -0
  110. package/bundle/release-notes/1.10.0.mdx +43 -0
  111. package/bundle/release-notes/1.11.0.mdx +19 -0
  112. package/bundle/release-notes/1.12.0.mdx +21 -0
  113. package/bundle/release-notes/1.13.0.mdx +43 -0
  114. package/bundle/release-notes/1.14.0.mdx +29 -0
  115. package/bundle/release-notes/1.15.0.mdx +56 -0
  116. package/bundle/release-notes/1.16.0.mdx +55 -0
  117. package/bundle/release-notes/1.17.0.mdx +15 -0
  118. package/bundle/release-notes/1.18.0.mdx +24 -0
  119. package/bundle/release-notes/1.19.0.mdx +48 -0
  120. package/bundle/release-notes/1.19.1.mdx +9 -0
  121. package/bundle/release-notes/1.2.0.mdx +80 -0
  122. package/bundle/release-notes/1.20.0.mdx +48 -0
  123. package/bundle/release-notes/1.21.0.mdx +66 -0
  124. package/bundle/release-notes/1.22.0.mdx +75 -0
  125. package/bundle/release-notes/1.23.0.mdx +50 -0
  126. package/bundle/release-notes/1.24.0.mdx +59 -0
  127. package/bundle/release-notes/1.25.0.mdx +52 -0
  128. package/bundle/release-notes/1.26.0.mdx +11 -0
  129. package/bundle/release-notes/1.3.0.mdx +21 -0
  130. package/bundle/release-notes/1.4.0.mdx +37 -0
  131. package/bundle/release-notes/1.5.0.mdx +24 -0
  132. package/bundle/release-notes/1.6.0.mdx +37 -0
  133. package/bundle/release-notes/1.6.1.mdx +11 -0
  134. package/bundle/release-notes/1.7.0.mdx +29 -0
  135. package/bundle/release-notes/1.8.0.mdx +38 -0
  136. package/bundle/release-notes/1.9.0.mdx +28 -0
  137. package/dist/index.d.ts +2 -0
  138. package/dist/index.js +324 -0
  139. package/dist/index.js.map +1 -0
  140. package/package.json +51 -0
@@ -0,0 +1,224 @@
1
+ ---
2
+ component: TreeView
3
+ category: Data Display
4
+ figma-node: ""
5
+ figma-file: ""
6
+ related: [AbstractTree]
7
+ tokens: []
8
+ stories: stories/TreeView/TreeView.stories.tsx
9
+ version: "1.0"
10
+ ---
11
+
12
+ # TreeView
13
+
14
+ ## Назначение
15
+
16
+ Базовый каркас визуального дерева: семантическая разметка
17
+ (`<ul role="tree">` + `<li role="treeitem">` + `<ul role="group">`) и
18
+ keyboard navigation по вертикали (ArrowUp/ArrowDown). Параллельно
19
+ поддерживает абстрактное дерево (`TreeViewAbstractNode`) для трекинга
20
+ active/opened/tabIndex состояний нод. Контент каждой ноды — render-prop
21
+ (заголовок, иконка, custom-обёртка — на потребителе).
22
+
23
+ Используй когда: нужно дерево с произвольным content'ом нод. Сам
24
+ TreeView не рисует expand-кнопку и chevron — потребитель использует
25
+ `hasChildNodes`/`openedNode`/`setOpenedNode` из render-prop'а.
26
+
27
+ ---
28
+
29
+ ## Файловая структура
30
+
31
+ ```text
32
+ src/components/TreeView/
33
+ ├── TreeView.tsx # class TreeView (Component) + ITreeViewComposition
34
+ ├── TreeViewContext.tsx # ITreeViewContext + withTreeViewContext HOC
35
+ ├── TreeViewAbstractNode.tsx # AbstractTreeNode + active/opened/tabIndex
36
+ ├── TreeViewAbstractNodeUtils.ts # навигация по абстрактному дереву
37
+ ├── components/
38
+ │ ├── TreeViewNode.tsx # class TreeViewNodeWithContext (Component) + withTreeViewContext
39
+ │ └── TreeViewGroup.tsx # FC, <ul role="group">
40
+ └── styles/TreeView.module.less
41
+ ```
42
+
43
+ **Примечание:** в директории нет `index.ts` — импортировать из `./TreeView`,
44
+ `./components/TreeViewNode`, `./components/TreeViewGroup` напрямую (или из
45
+ точки экспорта дизайн-системы, если она настроена в `src/components/index.ts`).
46
+
47
+ ---
48
+
49
+ ## Композиция
50
+
51
+ ```tsx
52
+ <TreeView>
53
+ <TreeView.Node id="1" prevNodeId={undefined} nextNodeId="2">
54
+ {({ activeNode, openedNode, hasChildNodes, isLastNode, setOpenedNode }) => (
55
+ <>
56
+ <button onClick={() => setOpenedNode(!openedNode)}>
57
+ {hasChildNodes ? (openedNode ? "▾" : "▸") : "•"} Node 1
58
+ </button>
59
+ {openedNode && (
60
+ <TreeView.Group>
61
+ <TreeView.Node id="1.1">{/* ... */}</TreeView.Node>
62
+ </TreeView.Group>
63
+ )}
64
+ </>
65
+ )}
66
+ </TreeView.Node>
67
+ <TreeView.Node id="2" prevNodeId="1">{/* ... */}</TreeView.Node>
68
+ </TreeView>
69
+ ```
70
+
71
+ ---
72
+
73
+ ## Публичный API
74
+
75
+ ### `TreeView` (class component)
76
+
77
+ | Prop | Тип | Описание |
78
+ |---|---|---|
79
+ | `children` | `ReactNode` | Дерево из `TreeView.Node` / `TreeView.Group`. |
80
+ | `...HTMLAttributes<HTMLUListElement>` | — | На корневой `<ul role="tree">`. |
81
+
82
+ Композиция: `TreeView.Node = TreeViewNode`, `TreeView.Group = TreeViewGroup`.
83
+
84
+ ### `TreeView.Node` (`ITreeViewNodeProps`)
85
+
86
+ ```ts
87
+ extends Omit<HTMLAttributes<HTMLElement>, "children">
88
+ ```
89
+
90
+ | Prop | Тип | Описание |
91
+ |---|---|---|
92
+ | `id` | `string` | **Обязателен**. Уникальный id ноды. |
93
+ | `children` | `(props: ITreeViewNodeProvideProps) => JSX.Element` | Render-prop — потребитель рисует содержимое. |
94
+ | `opened?` | `boolean` | Внешнее управление раскрытием. Синхронизируется через `componentDidUpdate`. |
95
+ | `prevNodeId?` | `string` | Id соседа слева (для корректной вставки в абстрактное дерево). |
96
+ | `nextNodeId?` | `string` | Id соседа справа. |
97
+ | `...HTMLAttributes<HTMLElement>` | — | На `<li role="treeitem">`. |
98
+
99
+ #### `ITreeViewNodeProvideProps` (render-prop)
100
+
101
+ | Поле | Тип | Описание |
102
+ |---|---|---|
103
+ | `activeNode` | `boolean` | Нода активна для keyboard-навигации. |
104
+ | `openedNode` | `boolean` | Раскрыта. |
105
+ | `hasChildNodes` | `boolean` | Есть вложенные `TreeView.Node`. |
106
+ | `isLastNode` | `boolean` | Последняя в полном обходе дерева. |
107
+ | `setOpenedNode` | `(opened: boolean) => void` | Переключение opened. |
108
+
109
+ ### `TreeView.Group` (`ITreeViewGroupProps`)
110
+
111
+ ```ts
112
+ extends HTMLAttributes<HTMLUListElement>
113
+ ```
114
+
115
+ `<ul role="group">` — обёртка для вложенных нод.
116
+
117
+ ### `TreeViewContext` / `withTreeViewContext` / `IWithTreeViewContextProps`
118
+
119
+ Экспорт контекста и HOC. `withTreeViewContext<T>(Component)` оборачивает
120
+ класс-компонент, передавая `treeViewContext: ITreeViewContext` как props.
121
+
122
+ ### `TreeViewAbstractNode` (class)
123
+
124
+ Расширяет `AbstractTreeNode<TreeViewAbstractNode>`:
125
+
126
+ | Метод | Назначение |
127
+ |---|---|
128
+ | `getId()` / id фиксирован в конструкторе | — |
129
+ | `getActive()` / `setActive(boolean)` | Активность для keyboard. |
130
+ | `getOpened()` / `setOpened(boolean)` | Раскрытие. |
131
+ | `getTabIndex()` / `setTabIndex(n)` | Управление roving tabindex (0 для первой ноды первого уровня, -1 — для остальных). |
132
+
133
+ ### `TreeViewAbstractNodeUtils`
134
+
135
+ Утилиты обхода: `getNode`, `setActiveNode`, `setActiveNextNode`,
136
+ `setActivePrevNode`, `getActiveNode`, `isLastNode`.
137
+
138
+ ---
139
+
140
+ ## Поведение
141
+
142
+ ### Жизненный цикл ноды
143
+
144
+ `TreeViewNodeWithContext` (class):
145
+ - **constructor:** создаёт `TreeViewAbstractNode({id})`, вызывает `treeViewContext.setOpenedNode(...)` + `registerNode(node, parentNode, prevNode, nextNode)`.
146
+ - **componentDidUpdate:** при изменении `opened` props синхронизирует с абстрактной нодой.
147
+ - **componentWillUnmount:** `treeViewContext.removeNode(node)`.
148
+
149
+ ### Контекст
150
+
151
+ Каждая нода **переопределяет** `parentNode` в `TreeViewContext.Provider`,
152
+ чтобы дочерние `TreeView.Node` регистрировались в правильном родителе.
153
+
154
+ ### Keyboard navigation
155
+
156
+ `TreeView.componentDidMount` вешает `window.addEventListener("keydown")`:
157
+ - ArrowDown → `setActiveNextNode` + `preventDefault` + триггер ререндера.
158
+ - ArrowUp → `setActivePrevNode`.
159
+ - Срабатывает только если есть активная нода (`getActiveNode != undefined`).
160
+
161
+ ### Active state и focus
162
+
163
+ - `<li>` `tabIndex` берётся из `abstractNode.getTabIndex()` (0 для первой первого уровня, -1 — остальных).
164
+ - `onFocus` (event.stopPropagation, чтобы родитель не получил bubble) → `setActiveNode(node, true)`.
165
+ - `onBlur` → `setActiveNode(node, false)`.
166
+
167
+ ### `updateCount` в state
168
+
169
+ Числовой счётчик, увеличивается при `registerNode` / `setActiveNode` /
170
+ `setOpenedNode` / keyboard навигации. Триггерит ререндер контекста, т.к.
171
+ `abstractNode` — мутируемый объект (изменения не вызывают триггер
172
+ самостоятельно).
173
+
174
+ ⚠️ В коде `prevCount++` (post-increment) — возвращает старое значение,
175
+ поэтому фактически `updateCount` не растёт. Тем не менее `setState` всё
176
+ равно вызывает ререндер, поэтому работает.
177
+
178
+ ---
179
+
180
+ ## Инварианты
181
+
182
+ - **`TreeView` и `TreeViewNode` — class components**, не FC и не forwardRef. Рефы на `<ul>`/`<li>` приходят через `htmlAttributes` (но не пробрасываются явно).
183
+ - **`TreeView.Node`'s `id` обязателен и должен быть уникальным** во всём дереве.
184
+ - **`prevNodeId`/`nextNodeId` опциональны**, но без них вставка в абстрактное дерево идёт в конец (порядок может разойтись с DOM при динамическом добавлении).
185
+ - **Render-prop `children`** — обязателен (`(props) => JSX.Element`).
186
+ - **`<li role="treeitem">`** + `<ul role="group">` + `<ul role="tree">` — фиксированная семантика по WAI-ARIA `treeview-2a`.
187
+ - **Keyboard listener на `window`** — может конфликтовать с другими компонентами; для multiple TreeView'ов обработчики складываются (срабатывает первый с активной нодой).
188
+ - **Mutable `abstractNode`** — изменения требуют ручного `setState({updateCount})` для ререндера. Не доставать ссылку из contextValue для дальнейшей мутации без `setState`.
189
+ - **`event.stopPropagation` в onFocus/onBlur ноды** — обязательно, чтобы родительский `<li>` не получил bubble.
190
+ - **Композиция через `TreeView.Node = TreeViewNode`** — статические свойства класса.
191
+ - **Опечатка в `prevCount++` (vs `++prevCount`)** — намеренно или нет, не править без анализа поведения.
192
+ - **Нет `index.ts`** в директории — импорты идут напрямую из файлов компонентов.
193
+
194
+ ---
195
+
196
+ ## Accessibility
197
+
198
+ - Реализовано по WAI-ARIA `treeview-2a` pattern: https://www.w3.org/TR/wai-aria-practices-1.1/examples/treeview/treeview-2/treeview-2a.html
199
+ - `role="tree"` на корне, `role="treeitem"` на `<li>`, `role="group"` на `<ul>`.
200
+ - `aria-expanded={openedNode}` на каждом `<li>`.
201
+ - Roving tabindex (только первая нода первого уровня в tab-стопе).
202
+ - ArrowDown/ArrowUp — навигация по дереву (только при наличии активной ноды).
203
+ - ArrowLeft/ArrowRight (свернуть/раскрыть) — НЕ реализованы; должен делать потребитель в render-prop'е через `setOpenedNode`.
204
+
205
+ ---
206
+
207
+ ## Связанные компоненты
208
+
209
+ - `AbstractTree` / `AbstractTreeNode` — базовый класс для абстрактного дерева.
210
+ - Любой потребитель — рисует UI ноды (заголовок, кнопка, иконка expand) через render-prop.
211
+
212
+ ---
213
+
214
+ ## Stories
215
+
216
+ Основные истории: `stories/TreeView/TreeView.stories.tsx`
217
+
218
+ ---
219
+
220
+ ## История изменений
221
+
222
+ | Дата | Изменение |
223
+ |---|---|
224
+ | 2026-04-18 | Создан документ |
@@ -0,0 +1,121 @@
1
+ ---
2
+ component: Triggers
3
+ category: Utility
4
+ figma-node: ""
5
+ figma-file: ""
6
+ related: [KeyDownListener]
7
+ tokens: []
8
+ stories: ""
9
+ version: "1.0"
10
+ ---
11
+
12
+ # Triggers (TriggerClickOnKeyDownEvent)
13
+
14
+ ## Назначение
15
+
16
+ Утилитарный компонент: при нажатии клавиш(и) программно вызывает `.click()`
17
+ на элементе по `targetRef`. Тонкая обёртка над `KeyDownListener` для
18
+ типового сценария "при Enter/Esc/etc — кликни кнопку".
19
+
20
+ Используй когда: нужно сделать глобальный hotkey для существующей кнопки
21
+ без её рефакторинга. Для произвольного обработчика — использовать
22
+ `KeyDownListener` напрямую.
23
+
24
+ ---
25
+
26
+ ## Файловая структура
27
+
28
+ ```text
29
+ src/components/Triggers/
30
+ └── TriggerClickOnKeyDownEvent.tsx # FC<ITriggerClickOnKeyDownEventProps>
31
+ ```
32
+
33
+ **Примечание:** в директории нет `index.ts`. Импортировать напрямую:
34
+ `import { TriggerClickOnKeyDownEvent } from ".../Triggers/TriggerClickOnKeyDownEvent"`.
35
+
36
+ ---
37
+
38
+ ## Публичный API
39
+
40
+ ### `ITriggerClickOnKeyDownEventProps`
41
+
42
+ ```ts
43
+ extends Pick<IKeyDownListenerProps, "eventKeyCode">
44
+ ```
45
+
46
+ | Prop | Тип | Описание |
47
+ |---|---|---|
48
+ | `eventKeyCode` | `keyCode \| keyCode[]` | Код клавиши (`EVENT_KEY_CODES.ENTER`, `.ESCAPE`, ...) или массив. |
49
+ | `targetRef` | `RefObject<HTMLButtonElement>` | **Обязателен**. Ссылка на кнопку, которая будет кликнута. |
50
+ | `children` | `ReactElement` | Прокидывается в `KeyDownListener` (обычно — само поддерево, в котором находится targetRef). |
51
+
52
+ `keyCode` приходит из `EVENT_KEY_CODES` (`utils/keyboard`).
53
+
54
+ ---
55
+
56
+ ## Поведение
57
+
58
+ ```tsx
59
+ <TriggerClickOnKeyDownEvent eventKeyCode={EVENT_KEY_CODES.ENTER} targetRef={btnRef}>
60
+ <Form>
61
+ <button ref={btnRef}>Submit</button>
62
+ </Form>
63
+ </TriggerClickOnKeyDownEvent>
64
+ ```
65
+
66
+ Внутри:
67
+ ```tsx
68
+ <KeyDownListener onMatch={handleKeyDown} eventKeyCode={eventKeyCode}>
69
+ {children}
70
+ </KeyDownListener>
71
+ ```
72
+
73
+ `handleKeyDown`:
74
+ 1. Берёт `targetRef.current`.
75
+ 2. Если кнопки нет → return.
76
+ 3. Проверяет `buttonEl.offsetParent !== null` (элемент видим — нет `display: none` на нём или предке).
77
+ 4. Вызывает `buttonEl.click()`.
78
+
79
+ `KeyDownListener` слушает `keydown` на `window`.
80
+
81
+ ---
82
+
83
+ ## Инварианты
84
+
85
+ - **`FC, не forwardRef`** — ref не пробрасывается.
86
+ - **`targetRef` обязателен** — без него ничего не произойдёт.
87
+ - **Только `HTMLButtonElement`** — ref типизирован специфично; для других элементов поведение не гарантировано (хоть `.click()` есть на любом HTMLElement).
88
+ - **Проверка `offsetParent !== null`** — защита от триггера на скрытых кнопках. Не путать со `visibility: hidden` (offsetParent остаётся не-null) — для них кнопка всё равно кликнется.
89
+ - **`displayName` выставлен**.
90
+ - **Нет `index.ts`** — импорты напрямую из файла.
91
+ - **`KeyDownListener` слушает на `window`** — глобальный обработчик. Множество `TriggerClickOnKeyDownEvent` со стороны одной и той же клавиши все сработают.
92
+
93
+ ---
94
+
95
+ ## Accessibility
96
+
97
+ - Программный клик — не заменяет фокус-управление; для accessibility важно, чтобы кнопка была доступна и через Tab.
98
+ - Если кнопка `disabled` — `.click()` не сработает (браузерная защита).
99
+ - Hotkey не объявляется через ARIA — потребитель сам сообщает пользователю о горячих клавишах (например, через подсказку в UI).
100
+
101
+ ---
102
+
103
+ ## Связанные компоненты
104
+
105
+ - `KeyDownListener` — базовый компонент-слушатель `keydown` на `window`.
106
+ - `ComposedKeyDownListener` — комбинация нескольких слушателей.
107
+ - `EVENT_KEY_CODES` (`utils/keyboard`) — константы клавиш.
108
+
109
+ ---
110
+
111
+ ## Stories
112
+
113
+ Отдельных stories нет — компонент используется как утилита внутри других сценариев.
114
+
115
+ ---
116
+
117
+ ## История изменений
118
+
119
+ | Дата | Изменение |
120
+ |---|---|
121
+ | 2026-04-18 | Создан документ |
@@ -0,0 +1,205 @@
1
+ # Codestyle — Triplex-Next
2
+
3
+ Правила написания кода для AI-агентов и разработчиков.
4
+ Линтер и форматтер применяются автоматически при коммите через `lint-staged`.
5
+
6
+ ---
7
+
8
+ ## Форматирование
9
+
10
+ Не нужно форматировать вручную — `lint-staged` запустит `prettier --write` при `git commit`.
11
+
12
+ ---
13
+
14
+ ## TypeScript
15
+
16
+ ### Основные правила
17
+
18
+ - `strict: true` включён — никаких лазеек.
19
+ - **Запрещено:** `any`. Использовать `unknown` + type guard или точный тип.
20
+ - **Запрещено:** non-null assertion (`!`) без объясняющего комментария.
21
+ - **Запрещено:** `as Type` без крайней необходимости. Предпочитать `satisfies`.
22
+
23
+ В компонентной библиотеке нельзя бросать исключения в рантайме — они упадут у конечного пользователя приложения. Всегда предпочитай правильную типизацию или fallback-значение.
24
+
25
+ ### Именование
26
+
27
+ | Сущность | Префикс | Пример |
28
+ |---|---|---|
29
+ | Interface | `I` | `IButtonProps`, `IButtonGeneralProps` |
30
+ | Union type | `T` | `TButtonProps` |
31
+ | Enum | `E` | `EButtonTheme`, `EComponentSize` |
32
+ | Константа (модульного уровня) | UPPER_SNAKE_CASE | `DEFAULT_TIMEOUT` |
33
+ | Компонент | PascalCase | `Button`, `DateField` |
34
+ | Хук | camelCase с `use` | `useClickOutside` |
35
+ | Утилита | camelCase | `formatDate`, `uniqueId` |
36
+
37
+ ### Интерфейсы компонента
38
+
39
+ - Экспортируй интерфейс props — он часть публичного API.
40
+ - Явно указывай тип children если компонент его принимает (`React.ReactNode`).
41
+
42
+ ### Типизация пропов с вариантами (discriminated union)
43
+
44
+ Если компонент имеет несколько несовместимых тем/режимов — используй discriminated union:
45
+
46
+ ```typescript
47
+ export interface IButtonGeneralProps extends IButtonBaseProps {
48
+ theme: EButtonTheme.GENERAL | EButtonTheme.SECONDARY;
49
+ size: EComponentSize;
50
+ }
51
+
52
+ export interface IButtonLinkProps extends IButtonBaseProps {
53
+ theme: EButtonTheme.LINK;
54
+ type?: never; // LINK-кнопка не имеет type
55
+ }
56
+
57
+ export type TButtonProps = IButtonGeneralProps | IButtonLinkProps;
58
+ ```
59
+
60
+ ---
61
+
62
+ ## React
63
+
64
+ ### Обязательные паттерны
65
+
66
+ - **`forwardRef`** — обязателен на всех UI-компонентах.
67
+ - **Функциональные компоненты** — только. Классовые компоненты не используются.
68
+ - **`clsx`** — для объединения className. Никогда не конкатенировать строки.
69
+
70
+ ```typescript
71
+ // ❌
72
+ className={`${styles.button} ${size === 'sm' ? styles.sm : ''}`}
73
+
74
+ // ✅
75
+ className={clsx(styles.button, styles[size], { [styles.loading]: loading }, className)}
76
+ ```
77
+
78
+ - Всегда принимай `className` в пропах и прокидывай его в корневой элемент (через spread или явно).
79
+ - Всегда прокидывай `...rest` на семантический элемент.
80
+
81
+ ### Составные компоненты (Compound Components)
82
+
83
+ Для сложных компонентов с несколькими взаимосвязанными частями (Select + Option, Tabs + Tab + TabPanel) используй паттерн составных компонентов:
84
+
85
+ ```typescript
86
+ // Статические субкомпоненты через Object.assign
87
+ export const Select = Object.assign(
88
+ React.forwardRef<HTMLDivElement, ISelectProps>((props, ref) => {
89
+ // ...
90
+ }),
91
+ { Option: SelectOption, Group: SelectGroup }
92
+ );
93
+
94
+ // Использование:
95
+ <Select value={value} onChange={onChange}>
96
+ <Select.Option value="a">Option A</Select.Option>
97
+ </Select>
98
+ ```
99
+
100
+ ### Импорты
101
+
102
+ В stories/examples и тестах правила разные:
103
+
104
+ ```typescript
105
+ // ✅ В stories/examples, предназначенных для копирования
106
+ import { Button, EButtonTheme } from "@sberbusiness/triplex-next";
107
+
108
+ // ✅ Внутри src/components
109
+ import { EComponentSize } from "@sberbusiness/triplex-next/enums";
110
+
111
+ // ✅ В unit-тестах можно импортировать локально, следуя паттерну файла
112
+ import { Button } from "../Button";
113
+ ```
114
+
115
+ Правила:
116
+ - В новых stories/examples, которые показывают копируемый код, импортируй публичный API из `@sberbusiness/triplex-next` или публичных subentry.
117
+ - В unit-тестах и внутреннем коде следуй локальному паттерну файла и предпочитай ближайшие импорты для тестируемого модуля и внутренних зависимостей.
118
+
119
+ Порядок импортов (prettier-plugin-organize-imports не используется, но придерживайся):
120
+ 1. React
121
+ 2. Сторонние библиотеки
122
+ 3. Внутренние (`@sberbusiness/triplex-next/...`)
123
+ 4. Относительные импорты
124
+ 5. Стили
125
+
126
+ ---
127
+
128
+ ## LESS (CSS Modules)
129
+
130
+ ### Правила
131
+
132
+ - Только `.module.less` файлы — никаких глобальных стилей.
133
+ - Имена классов — **camelCase**: `styles.secondaryLight`, `styles.iconOnly`.
134
+ - Никаких **inline styles** в компонентах (в stories допустимо для лейаута примеров).
135
+ - **Цвета** — только через CSS-переменные токенов (`var(--triplex-next-...)`). Никаких hex/rgb.
136
+ - **Переиспользуемые размерные константы** (z-index, отступы страницы, ширины оверлеев) — через LESS-переменные из `src/styles/components/` (`@page-padding-desktop-x`, `@sideOverlayLGWidth` и т.д.). Компонент-специфичные значения (высота кнопки, радиус скругления) можно задавать литералом.
137
+ - `!important` — только в крайнем случае, с комментарием почему.
138
+
139
+
140
+ ### Применение LESS-переменных (общие размерные константы)
141
+
142
+ ```less
143
+ // ❌ дублируем магическую константу
144
+ .panel {
145
+ max-width: 864px;
146
+ }
147
+
148
+ // ✅ берём из src/styles/components/lightbox.less
149
+ @import (reference) "../../styles/components/lightbox";
150
+
151
+ .panel {
152
+ max-width: @lightBox-content-max-width-md;
153
+ }
154
+ ```
155
+
156
+ Если нужной константы в `src/styles/components/` нет, а значение используется более чем в одном компоненте — добавь переменную в подходящий файл.
157
+
158
+ ---
159
+
160
+ ## Accessibility
161
+
162
+ ### Мультиязычность: не хардкодь aria-текст
163
+
164
+ Дизайн-система используется в мультиязычных продуктах. **Никогда не пиши строки на конкретном языке внутри компонента** — ни в `aria-label`, ни в `title`, ни в любом другом атрибуте.
165
+
166
+ В stories добавляй пример значения для демонстрации — это не хардкод, а иллюстрация:
167
+
168
+ ```typescript
169
+ // В stories/examples/DefaultExample.tsx — пример для документации
170
+ <ModalCloseButton aria-label="Закрыть" />
171
+ ```
172
+
173
+ Если компоненту нужен внутренний текст для связи элементов (например, `aria-labelledby` на диалоге, ссылающийся на заголовок), используй ID-связку, а не текстовый литерал:
174
+
175
+ ### :focus-visible вместо :focus
176
+
177
+ ```less
178
+ // ❌ Показывает outline и при клике мышью
179
+ .button:focus {
180
+ outline: 2px solid var(--triplex-next-Button-General_Shadow_Focus);
181
+ }
182
+
183
+ // ✅ Только при клавиатурной навигации
184
+ .button:focus-visible {
185
+ outline: 2px solid var(--triplex-next-Button-General_Shadow_Focus);
186
+ }
187
+ ```
188
+
189
+ ---
190
+
191
+ ## Принципы написания кода
192
+
193
+ ### DRY (Don't Repeat Yourself)
194
+ ### KISS (Keep It Simple, Stupid)
195
+ ### YAGNI (You Aren't Gonna Need It)
196
+ ### Single Responsibility
197
+
198
+ ### Не добавляй без задачи
199
+
200
+ - Не добавляй docstring/комментарии к коду, который не изменял.
201
+ - Не рефакторь окружающий код без задачи.
202
+ - Не добавляй обработку ошибок для сценариев, которые не могут произойти.
203
+ - Не мигрируй legacy stories/tests на новый шаблон "по пути", если это не часть текущей задачи.
204
+
205
+ ---