@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,298 @@
1
+ ---
2
+ component: Pagination
3
+ category: Navigation
4
+ figma-node: ""
5
+ figma-file: ""
6
+ related: [SelectField, ButtonIcon, ButtonBase, Typography]
7
+ tokens: []
8
+ stories: stories/Pagination/Pagination.stories.tsx
9
+ version: "1.0"
10
+ ---
11
+
12
+ # Pagination
13
+
14
+ ## Назначение
15
+
16
+ Готовая навигация по страницам с двумя композитными слотами:
17
+ **`PaginationSelect`** (выбор количества элементов на странице) +
18
+ **`PaginationNavigation`** (стрелки prev/next и кнопки страниц с
19
+ ellipsis'ами). Сам `Pagination` — лишь сшивка через
20
+ `PaginationExtended` (контейнер `<nav>`), управление условным
21
+ рендером и проксирование props'ов.
22
+
23
+ `PaginationExtended` + примитивы (`PaginationNavigationExtended` /
24
+ `Item` / `PaginationPageButton` / `PaginationPageEllipsis` /
25
+ `PaginationNavigationButton`) экспортируются отдельно — для **кастомной
26
+ сборки** пагинации, когда стандартная не подходит.
27
+
28
+ Используй когда: типичная пагинация над таблицей/списком. Для
29
+ нестандартной — собирай из `PaginationExtended` + примитивов.
30
+
31
+ ---
32
+
33
+ ## Файловая структура
34
+
35
+ ```text
36
+ src/components/Pagination/
37
+ ├── Pagination.tsx # forwardRef; склейка Select + Navigation
38
+ ├── components/
39
+ │ ├── PaginationExtended.tsx # forwardRef; <nav> wrapper
40
+ │ ├── PaginationSelect.tsx # forwardRef; SelectField (size SM)
41
+ │ ├── PaginationNavigation.tsx # forwardRef; prev/pages/next + ellipsis
42
+ │ ├── PaginationNavigationExtended.tsx # forwardRef; <ul>
43
+ │ ├── PaginationNavigationExtendedItem.tsx # forwardRef; <li>
44
+ │ ├── PaginationNavigationButton.tsx # forwardRef; ButtonIcon SQUIRCLE + caret-icon
45
+ │ ├── PaginationPageButton.tsx # forwardRef; ButtonBase + Text B3
46
+ │ └── PaginationPageEllipsis.tsx # forwardRef; Text B3 "..."
47
+ ├── utils/paginationUtils.tsx # createPagesArray + generateRange + PAGINATION_ELLIPSIS_VALUE
48
+ ├── enums.ts # EPaginationNavigationIconDirection (BACK | NEXT)
49
+ ├── index.ts # Barrel: всё
50
+ ├── styles/*.module.less
51
+ └── __tests__/
52
+ ```
53
+
54
+ ---
55
+
56
+ ## Compound-структура
57
+
58
+ ```tsx
59
+ <Pagination
60
+ paginationSelectProps={{
61
+ paginationLabel: "На странице",
62
+ value: pageSize,
63
+ options: [
64
+ { value: 10, label: "10" },
65
+ { value: 25, label: "25" },
66
+ ],
67
+ onChange: setPageSize,
68
+ }}
69
+ paginationNavigationProps={{
70
+ currentPage,
71
+ totalPages,
72
+ onCurrentPageChange: setCurrentPage,
73
+ boundaryCount: 1,
74
+ siblingCount: 1,
75
+ }}
76
+ />
77
+ ```
78
+
79
+ Кастомная сборка:
80
+
81
+ ```tsx
82
+ <PaginationExtended>
83
+ <PaginationNavigationExtended>
84
+ <PaginationNavigationExtendedItem>
85
+ <PaginationNavigationButton direction={EPaginationNavigationIconDirection.BACK} />
86
+ </PaginationNavigationExtendedItem>
87
+ <PaginationNavigationExtendedItem>
88
+ <PaginationPageButton isCurrent>1</PaginationPageButton>
89
+ </PaginationNavigationExtendedItem>
90
+ ...
91
+ </PaginationNavigationExtended>
92
+ </PaginationExtended>
93
+ ```
94
+
95
+ ---
96
+
97
+ ## Варианты и props
98
+
99
+ ### Pagination — `IPaginationProps extends IPaginationExtendedProps`
100
+
101
+ | Prop | Тип | Описание |
102
+ |---|---|---|
103
+ | `paginationSelectProps?` | `IPaginationSelectProps` | Если **не передан** — Select не рендерится. |
104
+ | `paginationNavigationProps` | `IPaginationNavigationProps` | Обязателен. Если `totalPages <= 1` — Navigation не рендерится. |
105
+ | `...IPaginationExtendedProps` | `HTMLAttributes<HTMLElement>` | На `<nav>`. |
106
+
107
+ ### PaginationSelect — `IPaginationSelectProps`
108
+
109
+ ```ts
110
+ extends Omit<ISelectFieldProps, "size" | "value">
111
+ extends Required<Pick<ISelectFieldProps, "value">>
112
+ ```
113
+
114
+ | Prop | Тип | Описание |
115
+ |---|---|---|
116
+ | `paginationLabel` | `ReactNode` | Лейбл слева от селекта (`<Text size=B3 id={instanceId}>`). |
117
+ | `value` | `ISelectFieldProps["value"]` | Обязательно. |
118
+ | `options`, `onChange`, ... | от SelectField | — |
119
+
120
+ - **`size` захардкожен `EComponentSize.SM`**.
121
+ - **`mobileTitle = paginationLabel`** — тот же текст для мобильного header'а.
122
+ - **`targetProps.fieldLabel = ""`** — лейбл внутри SelectField очищается (label рисуется отдельно слева).
123
+ - **`aria-labelledby={instanceId}`** + `Text id={instanceId}` — связь label ↔ control.
124
+ - `instanceId = "Pagination-{lodash uniqueId()}"` — **SSR-небезопасно** (два рендера дадут разные id, гидрация может ругаться).
125
+
126
+ ### PaginationNavigation — `IPaginationNavigationProps extends HTMLAttributes<HTMLUListElement>`
127
+
128
+ | Prop | Тип | Default | Описание |
129
+ |---|---|---|---|
130
+ | `currentPage` | `number` | — | Контролируемая текущая страница (1-based). |
131
+ | `totalPages` | `number` | — | Всего страниц. |
132
+ | `onCurrentPageChange` | `(page: number) => void` | — | Сеттер. |
133
+ | `boundaryCount` | `number` | `0` | Сколько страниц всегда показывать в начале и в конце. |
134
+ | `siblingCount` | `number` | `0` | Сколько соседей слева/справа от текущей. |
135
+ | `buttonPrevProps?` | `Omit<IPaginationNavigationButtonProps, "direction" \| "children">` | — | Доп. props на кнопку «назад». |
136
+ | `buttonNextProps?` | `Omit<...>` | — | Доп. props на «вперёд». |
137
+
138
+ - **prev disabled при `currentPage === 1`** (или `buttonPrevProps?.disabled`).
139
+ - **next disabled при `currentPage === totalPages`** (или `buttonNextProps?.disabled`).
140
+ - Рендерит prev → массив страниц от `createPagesArray(...)` → next.
141
+
142
+ ### PaginationNavigationButton — `IPaginationNavigationButtonProps`
143
+
144
+ ```ts
145
+ extends Omit<IButtonIconProps, "shape" | "active" | "children">
146
+ children?: never
147
+ direction: EPaginationNavigationIconDirection
148
+ ```
149
+
150
+ - Иконка: `CaretleftStrokeSrvIcon24 paletteIndex={5}`. Для `NEXT` — добавляется `styles.directionIconNext` (поворот через CSS).
151
+ - `shape={EButtonIconShape.SQUIRCLE}` — захардкожен.
152
+ - `children?: never` — фиксированная иконка.
153
+
154
+ ### PaginationPageButton — `TPaginationPageButtonProps`
155
+
156
+ | Prop | Тип | Описание |
157
+ |---|---|---|
158
+ | `isCurrent?` | `boolean` | Подсветка текущей. **`aria-live="polite"`** при `isCurrent` — screen reader озвучит при смене. |
159
+ | `children` | `ReactNode` | Номер страницы. |
160
+ | `onClick` | `() => void` | Клик. |
161
+
162
+ - Содержимое — `<Text size=B3>`.
163
+
164
+ ### PaginationPageEllipsis — `IPaginationPageEllipsis extends HTMLAttributes<HTMLSpanElement>`
165
+
166
+ - `<Text size=B3>...children...</Text>`. Контракт: `children = "..."` — чисто визуальный плейсхолдер.
167
+
168
+ ### PaginationExtended — `IPaginationExtendedProps extends HTMLAttributes<HTMLElement>`
169
+
170
+ - `<nav>` контейнер. `data-tx={pkg.version}`.
171
+
172
+ ### PaginationNavigationExtended/Item
173
+
174
+ - `<ul>` / `<li>` — семантический список для пагинации.
175
+
176
+ ### enums.ts
177
+
178
+ - `EPaginationNavigationIconDirection.BACK | NEXT` (numeric enum: 0/1).
179
+
180
+ ---
181
+
182
+ ## Ключевые особенности реализации
183
+
184
+ ### Условный рендер слотов
185
+
186
+ ```tsx
187
+ {paginationSelectProps && <PaginationSelect {...paginationSelectProps} />}
188
+ {paginationNavigationProps.totalPages > 1 && <PaginationNavigation {...paginationNavigationProps} />}
189
+ ```
190
+
191
+ - Select — опциональный.
192
+ - Navigation — рендерится только при `totalPages > 1` (на 1 странице — нечего листать).
193
+
194
+ ### `PaginationUtils.createPagesArray`
195
+
196
+ ```ts
197
+ // startPages | (ellipsis | next page) | siblings around current | (ellipsis | prev page) | endPages
198
+ ```
199
+
200
+ - `boundaryCount` — кол-во **всегда видимых** в начале/конце.
201
+ - `siblingCount` — кол-во **соседей** вокруг текущей.
202
+ - Если соседи примыкают к boundary — ellipsis заменяется на одну страницу (без «1 ... 2 3 4» — будет «1 2 3 4»).
203
+ - Возвращает массив `number[]`, где **`PAGINATION_ELLIPSIS_VALUE = -1`** — sentinel для ellipsis.
204
+ - Использует `lodash-es/range`.
205
+
206
+ ### Sentinel `-1` для ellipsis
207
+
208
+ ```ts
209
+ case PAGINATION_ELLIPSIS_VALUE: return <PaginationPageEllipsis>...</PaginationPageEllipsis>;
210
+ default: return <PaginationPageButton>{page}</PaginationPageButton>;
211
+ ```
212
+
213
+ - Магическое значение `-1` — публично экспортируется как `PAGINATION_ELLIPSIS_VALUE`.
214
+
215
+ ### `aria-live="polite"` на текущей кнопке
216
+
217
+ ```tsx
218
+ aria-live={isCurrent ? "polite" : undefined}
219
+ ```
220
+
221
+ - При смене `currentPage` screen reader озвучит новую кнопку.
222
+
223
+ ### Иконка NEXT — поворот через CSS
224
+
225
+ ```tsx
226
+ {isDirectionBack ? <CaretleftStrokeSrvIcon24 ... /> : <CaretleftStrokeSrvIcon24 className={styles.directionIconNext} />}
227
+ ```
228
+
229
+ - Только одна иконка (`CaretleftStrokeSrvIcon24`); для NEXT — поворот на 180° через `styles.directionIconNext`.
230
+
231
+ ### `aria-labelledby` через `uniqueId`
232
+
233
+ ```ts
234
+ const [instanceId] = useState(() => `Pagination-${uniqueId()}`);
235
+ ```
236
+
237
+ - **SSR-небезопасно**: lodash `uniqueId` глобальный счётчик.
238
+ - Решает связь Text-label ↔ SelectField через `aria-labelledby`.
239
+
240
+ ---
241
+
242
+ ## Инварианты
243
+
244
+ - **`Pagination.totalPages > 1`** — Navigation рендерится; иначе скрыт.
245
+ - **`paginationNavigationProps`** — обязательный prop (тип, не optional).
246
+ - **`paginationSelectProps`** — опционален; отсутствие = нет селекта.
247
+ - **`PAGINATION_ELLIPSIS_VALUE = -1`** — публичная константа-sentinel.
248
+ - **`PaginationSelect.size = SM`** — захардкожен.
249
+ - **`PaginationSelect.targetProps.fieldLabel = ""`** — внутренний label очищается.
250
+ - **`PaginationNavigationButton.shape = SQUIRCLE`**, иконка `CaretleftStrokeSrvIcon24` — фиксированы; `children: never`.
251
+ - **`PaginationPageButton.aria-live="polite"`** при `isCurrent` — публичный a11y-контракт.
252
+ - **`uniqueId()`** в SelectId — SSR-небезопасно.
253
+ - **`<nav>` корень PaginationExtended** + `<ul>`/`<li>` для Navigation — семантическая структура.
254
+ - **`createPagesArray` зависит от lodash-es `range`**.
255
+ - **`EPaginationNavigationIconDirection`** — numeric enum (0/1), не string.
256
+ - **ForwardRef** на всех уровнях.
257
+
258
+ ---
259
+
260
+ ## Accessibility
261
+
262
+ - `<nav>` корень + `<ul>`/`<li>` элементы — корректная семантика навигации/списка.
263
+ - `aria-live="polite"` на текущей странице — озвучивание при смене.
264
+ - `aria-labelledby` связывает label PaginationSelect с самим SelectField.
265
+ - **Нет `aria-label="Pagination"`** на `<nav>` — потребитель должен передать через `...rest` (иначе screen reader скажет просто «navigation»).
266
+ - **Иконки prev/next без `aria-label`** — `ButtonIcon` сам по себе не озвучивает направление. Потребитель должен передать `buttonPrevProps={{ "aria-label": "Предыдущая" }}` и т.п.
267
+ - `disabled` корректно через `<button disabled>` — клавиатура и SR обработают.
268
+
269
+ ---
270
+
271
+ ## Дизайн-токены
272
+
273
+ Нет собственных — наследуются от `Button`, `Text`, `SelectField`.
274
+
275
+ ---
276
+
277
+ ## Связанные компоненты
278
+
279
+ - `SelectField` — нижележащий control в PaginationSelect.
280
+ - `ButtonIcon`, `ButtonBase` — кнопки.
281
+ - `Text` (Typography) — текст.
282
+ - `CaretleftStrokeSrvIcon24` — единственная иконка стрелки.
283
+ - `lodash-es/range`, `lodash-es/uniqueId`.
284
+
285
+ ---
286
+
287
+ ## Stories
288
+
289
+ `stories/Pagination/Pagination.stories.tsx`
290
+ Примеры: `stories/Pagination/examples/`
291
+
292
+ ---
293
+
294
+ ## История изменений
295
+
296
+ | Дата | Изменение |
297
+ |---|---|
298
+ | 2026-04-18 | Создан документ |
@@ -0,0 +1,119 @@
1
+ ---
2
+ component: Portal
3
+ category: Utilities
4
+ figma-node: ""
5
+ figma-file: ""
6
+ related: [ModalWindow, LightBox, Dropdown, Tooltip]
7
+ tokens: []
8
+ stories: ""
9
+ version: "1.0"
10
+ ---
11
+
12
+ # Portal
13
+
14
+ ## Назначение
15
+
16
+ Тонкая обёртка над `ReactDOM.createPortal`. Рендерит `children` во
17
+ внешний DOM-узел (`container`), оставаясь в текущем React-дереве
18
+ (контекст, обработчики, focus-trap работают как обычно).
19
+
20
+ Используй когда: нужен node вне родительского дерева (обход
21
+ `overflow: hidden`, `z-index`-стэк, поверх модалок). В большинстве
22
+ случаев напрямую — внутри `ModalWindow`/`Dropdown`/`Tooltip`/`LightBox`.
23
+
24
+ ---
25
+
26
+ ## Файловая структура
27
+
28
+ ```text
29
+ src/components/Portal/
30
+ ├── Portal.tsx # FC, 1 строка — createPortal
31
+ └── index.ts # Barrel
32
+ ```
33
+
34
+ Нет стилей, тестов, stories.
35
+
36
+ ---
37
+
38
+ ## Варианты и props
39
+
40
+ ### `IPortalProps`
41
+
42
+ | Prop | Тип | Обязателен | Описание |
43
+ |---|---|---|---|
44
+ | `children` | `ReactNode` | Да | Рендерится в `container`. |
45
+ | `container` | `Element \| DocumentFragment` | Да | Целевой DOM-узел. |
46
+
47
+ Никаких опциональных props или fallback'ов.
48
+
49
+ ---
50
+
51
+ ## Ключевые особенности реализации
52
+
53
+ ### `ReactDOM.createPortal`
54
+
55
+ ```tsx
56
+ export const Portal = ({ children, container }) => ReactDOM.createPortal(children, container);
57
+ ```
58
+
59
+ - React сам управляет mount/unmount через виртуальное дерево.
60
+ - **Контекст-провайдеры**, **events bubbling** работают, как если бы дерево было локальным (events bubble через React-tree, не DOM-tree).
61
+
62
+ ### `container` — обязателен
63
+
64
+ - В отличие от ряда библиотек, `Portal` **не создаёт** контейнер сам и **не подбирает** `document.body` по умолчанию.
65
+ - Потребитель сам решает, куда монтировать (типичный паттерн: создать `<div>` в `document.body` и передать сюда).
66
+
67
+ ### **Не SSR-safe**
68
+
69
+ - `createPortal` на сервере выбрасывает ошибку. При SSR — потребитель должен оборачивать в `if (typeof window !== "undefined")` или использовать `useEffect`.
70
+ - В этом проекте — обычно через `useEffect` создают и монтируют контейнер (см. `ModalWindow`, `LightBox`).
71
+
72
+ ### **Никаких lifecycle-хуков**
73
+
74
+ - Portal не отслеживает изменение `container`. При смене `container` — React удалит старый child из старого container и смонтирует в новый (стандартное поведение createPortal).
75
+
76
+ ---
77
+
78
+ ## Инварианты
79
+
80
+ - **`container` — обязателен**, не optional.
81
+ - **`children` — `ReactNode`**, не `ReactElement` (можно массив, fragment, primitive).
82
+ - **`FC`, не `forwardRef`** — ref на children идёт нативно через React-tree, на сам Portal-component — нет.
83
+ - **Не SSR-safe** — потребитель ответственен за условный рендер.
84
+ - **Контейнер не создаётся автоматически** — приходит снаружи.
85
+ - **Никаких side effects** — нет `useEffect`, нет mount-классов, нет cleanup.
86
+
87
+ ---
88
+
89
+ ## Accessibility
90
+
91
+ - Portal не влияет на семантику. **Focus management, ARIA-связи** — задача потребителя.
92
+ - Tab-последовательность определяется DOM-порядком: portal'ed узел — там, куда смонтирован, не там, где находится в JSX. Это часто нарушает focus order — нужен focus-trap внутри.
93
+
94
+ ---
95
+
96
+ ## Дизайн-токены
97
+
98
+ Нет.
99
+
100
+ ---
101
+
102
+ ## Связанные компоненты
103
+
104
+ - `ModalWindow` / `LightBox` / `Dropdown` / `Tooltip` — все используют Portal.
105
+ - `react-dom` `createPortal` — нативный API.
106
+
107
+ ---
108
+
109
+ ## Stories
110
+
111
+ Нет.
112
+
113
+ ---
114
+
115
+ ## История изменений
116
+
117
+ | Дата | Изменение |
118
+ |---|---|
119
+ | 2026-04-18 | Создан документ |
@@ -0,0 +1,174 @@
1
+ ---
2
+ component: Radio
3
+ category: Form Controls
4
+ figma-node: ""
5
+ figma-file: ""
6
+ related: [Checkbox, RadioYGroup, RadioXGroup, Typography, FormField]
7
+ tokens: []
8
+ stories: stories/Radio/Radio.stories.tsx
9
+ version: "1.0"
10
+ ---
11
+
12
+ # Radio
13
+
14
+ ## Назначение
15
+
16
+ Радио-кнопка: нативный `<input type="radio">` с visually-hidden default'ом
17
+ + кастомная иконка (`<span>` со стилями) + опциональный текст-описание
18
+ (через `Text`). Сгруппировать кнопки можно горизонтально (`RadioXGroup`)
19
+ или вертикально (`RadioYGroup`) — оба контейнера дают `role="radiogroup"`.
20
+
21
+ Используй когда: выбор одного варианта из небольшого набора (если
22
+ больше ~5 — `SelectField`). Для multi-choice — `Checkbox` + `RadioYGroup`-
23
+ аналог.
24
+
25
+ ---
26
+
27
+ ## Файловая структура
28
+
29
+ ```text
30
+ src/components/Radio/
31
+ ├── Radio.tsx # forwardRef<HTMLInputElement>; <label> + <input> + <span> + <Text>
32
+ ├── RadioXGroup.tsx # FC; <div role="radiogroup"> + indent (12-32)
33
+ ├── RadioYGroup.tsx # FC; <div role="radiogroup">
34
+ ├── styles/Radio.module.less + RadioXGroup + RadioYGroup
35
+ ├── index.ts # Barrel
36
+ └── __tests__/
37
+ ```
38
+
39
+ ---
40
+
41
+ ## Варианты и props
42
+
43
+ ### Radio — `IRadioProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "type" | "size">`
44
+
45
+ | Prop | Тип | Default | Описание |
46
+ |---|---|---|---|
47
+ | `size` | `EComponentSize` | `MD` | Размер кнопки + связанный размер `Text`. |
48
+ | `labelAttributes?` | `LabelHTMLAttributes<HTMLLabelElement>` | — | Атрибуты на `<label>` (className мержится). |
49
+ | `children?` | `ReactNode` | — | Текст-описание справа от радио. Если пуст — текст не рендерится, label получает класс без `nonempty`. |
50
+ | `disabled?` | `boolean` | — | Прокидывается в `<input>` + добавляет класс `disabled` на label. |
51
+ | `...inputAttributes` | `InputHTMLAttributes<HTMLInputElement>` | — | Все остальные `<input>`-атрибуты (`name`, `value`, `checked`, `onChange`, ...). |
52
+
53
+ - **`type` исключён** — всегда `"radio"`.
54
+ - **`size` исключён из `InputHTMLAttributes`** — наш собственный enum, не нативный.
55
+ - ForwardRef → `<input>`.
56
+
57
+ ### Маппинг `size` → текст:
58
+
59
+ | `size` | `Text size` |
60
+ |---|---|
61
+ | `LG` | `B2` |
62
+ | `MD` | `B3` |
63
+ | `SM` | `B4` |
64
+
65
+ ### RadioXGroup — `IRadioXGroupProps extends HTMLAttributes<HTMLDivElement>`
66
+
67
+ | Prop | Тип | Default | Описание |
68
+ |---|---|---|---|
69
+ | `indent` | `TIndentSize` (`12 \| 16 \| 20 \| 24 \| 28 \| 32`) | `12` | Горизонтальный gap (в `px`, через CSS-классы `indent-{N}`). |
70
+ | `...HTMLAttributes` | — | — | На `<div>`. `role="radiogroup"`. |
71
+
72
+ ### RadioYGroup — `IRadioYGroupProps extends HTMLAttributes<HTMLDivElement>`
73
+
74
+ | Prop | Тип | Описание |
75
+ |---|---|---|
76
+ | `...HTMLAttributes` | — | На `<div>`. `role="radiogroup"`. Gap — фиксированный (см. CSS). |
77
+
78
+ ---
79
+
80
+ ## Ключевые особенности реализации
81
+
82
+ ### Native `<input>` + кастомный `<span>` (visually-hidden pattern)
83
+
84
+ ```tsx
85
+ <label>
86
+ <input type="radio" className={styles.radio} ... />
87
+ <span className={styles.radioIcon} />
88
+ {children && <Text>{children}</Text>}
89
+ </label>
90
+ ```
91
+
92
+ - `<input>` скрыт визуально (`appearance: none` / `opacity: 0` в стилях), но **остаётся в фокусной последовательности** и работает как нативный radio.
93
+ - `<span class="radioIcon">` — визуальная рамка/точка, реагирует на состояния `:checked`, `:disabled`, `:focus-visible` через CSS-селекторы вида `input:checked + .radioIcon`.
94
+
95
+ ### Текст-описание — через `<Text size>`
96
+
97
+ - Маппинг по `size`: SM→B4, MD→B3, LG→B2.
98
+ - Если `children` пуст — `<Text>` не рендерится (избегаем лишнего span).
99
+ - `nonempty` класс на label — позволяет CSS отличать "только иконка" от "иконка + текст" (например, для горизонтального gap'а).
100
+
101
+ ### `labelAttributes` — мерж className
102
+
103
+ ```ts
104
+ labelAttributes?.className → clsx(..., labelAttributes?.className)
105
+ ```
106
+
107
+ - Стандартный паттерн.
108
+
109
+ ### Группы — `role="radiogroup"`, **без `value`**
110
+
111
+ - `RadioXGroup` / `RadioYGroup` — **тонкие layout-обёртки**. Не управляют выбранным значением, не вешают `name` на дочерние radio. Контракт name/value/onChange — на потребителе.
112
+ - Это отличается от ряда библиотек (`MUI.RadioGroup` управляет состоянием) — здесь группа только семантическая + layout.
113
+
114
+ ### `RadioXGroup.indent` — фиксированный набор
115
+
116
+ - `TIndentSize = 12 | 16 | 20 | 24 | 28 | 32` (px). Добавляет класс `indent-{N}`.
117
+ - Default `12px`.
118
+
119
+ ---
120
+
121
+ ## Инварианты
122
+
123
+ - **`<input type="radio">`** — нативный, фиксирован. `type` исключён из props.
124
+ - **`size: EComponentSize` default `MD`** — наш enum, не размер `<input>`.
125
+ - **`children` — описание справа**, опционально. Без `children` рендерится только иконка.
126
+ - **Текст всегда `<Text tag="div" size>` с маппингом** SM→B4 / MD→B3 / LG→B2.
127
+ - **`labelAttributes` — единственный канал** для прокидывания `<label>`-атрибутов (`for`, `id`, `className`).
128
+ - **`RadioXGroup`/`RadioYGroup`** — layout + `role="radiogroup"`. Не управляют состоянием.
129
+ - **`RadioXGroup.indent` default `12`**, набор фиксирован `TIndentSize`.
130
+ - **ForwardRef → `<input>`** на Radio. Не на label/wrapper.
131
+ - **`RadioXGroup` / `RadioYGroup` — `FC`, не forwardRef**.
132
+ - **`data-tx`** — на `<label>`.
133
+
134
+ ---
135
+
136
+ ## Accessibility
137
+
138
+ - Нативный `<input type="radio">` — клавиатура (стрелки внутри той же `name`-группы), screen readers — работают «из коробки».
139
+ - **`<label>` оборачивает input** — клик по тексту/иконке тоже выбирает radio.
140
+ - `role="radiogroup"` на контейнере группы — корректная семантика для AT. Но **`aria-labelledby`** / `aria-label` группы нужно передавать через `...rest`.
141
+ - **`name`** — потребитель должен передавать одинаковый name на все радио в группе (иначе нативная группировка/стрелки не работают).
142
+ - `disabled` — корректно через `<input disabled>` (focus-skip, SR озвучивает).
143
+ - `:focus-visible` стили — через CSS-класс на `<span class="radioIcon">` (стандартный паттерн).
144
+
145
+ ---
146
+
147
+ ## Дизайн-токены
148
+
149
+ Нет собственных. Цвета/размер `<span class="radioIcon">` — из `styles/Radio.module.less` (часто переменные проекта; уточнить в LESS).
150
+
151
+ ---
152
+
153
+ ## Связанные компоненты
154
+
155
+ - `Checkbox` — параллельный компонент.
156
+ - `Text` (Typography) — описание.
157
+ - `RadioXGroup` / `RadioYGroup` — layout-группы.
158
+ - `EComponentSize`, `ETextSize`.
159
+ - `TIndentSize` (`consts/IndentConst`).
160
+
161
+ ---
162
+
163
+ ## Stories
164
+
165
+ `stories/Radio/Radio.stories.tsx`
166
+ Примеры: `stories/Radio/examples/`
167
+
168
+ ---
169
+
170
+ ## История изменений
171
+
172
+ | Дата | Изменение |
173
+ |---|---|
174
+ | 2026-04-18 | Создан документ |