@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.
- package/README.md +124 -0
- package/bundle/.gitkeep +0 -0
- package/bundle/components/AbstractTree.md +198 -0
- package/bundle/components/AccordionBase.md +227 -0
- package/bundle/components/Alert.md +317 -0
- package/bundle/components/Amount.md +225 -0
- package/bundle/components/AmountField.md +289 -0
- package/bundle/components/Avatar.md +209 -0
- package/bundle/components/Badge.md +277 -0
- package/bundle/components/Body.md +167 -0
- package/bundle/components/Button.md +230 -0
- package/bundle/components/ButtonDropdown.md +159 -0
- package/bundle/components/ButtonIcon.md +122 -0
- package/bundle/components/Calendar.md +445 -0
- package/bundle/components/Card.md +374 -0
- package/bundle/components/CarouselExtended.md +250 -0
- package/bundle/components/Checkbox.md +126 -0
- package/bundle/components/CheckboxTree.md +280 -0
- package/bundle/components/CheckboxTreeExtended.md +321 -0
- package/bundle/components/CheckboxXGroup.md +68 -0
- package/bundle/components/CheckboxYGroup.md +67 -0
- package/bundle/components/Chip.md +404 -0
- package/bundle/components/ChipGroup.md +179 -0
- package/bundle/components/Col.md +221 -0
- package/bundle/components/CollapsableTree.md +255 -0
- package/bundle/components/Confirm.md +343 -0
- package/bundle/components/DateField.md +371 -0
- package/bundle/components/DatePickerExtended.md +342 -0
- package/bundle/components/DateRange.md +275 -0
- package/bundle/components/DesignTokens.md +385 -0
- package/bundle/components/Divider.md +196 -0
- package/bundle/components/DocumentNumberEdit.md +280 -0
- package/bundle/components/Dropdown.md +433 -0
- package/bundle/components/Ellipsis.md +208 -0
- package/bundle/components/EmptyView.md +99 -0
- package/bundle/components/ExpandAnimation.md +281 -0
- package/bundle/components/Footer.md +339 -0
- package/bundle/components/FormField.md +326 -0
- package/bundle/components/FormGroup.md +99 -0
- package/bundle/components/Gap.md +113 -0
- package/bundle/components/Header.md +280 -0
- package/bundle/components/HelpBox.md +154 -0
- package/bundle/components/IconWrapper.md +145 -0
- package/bundle/components/Island.md +197 -0
- package/bundle/components/IslandAccordion.md +230 -0
- package/bundle/components/IslandWidget.md +233 -0
- package/bundle/components/KeyDownListener.md +157 -0
- package/bundle/components/LightBox.md +322 -0
- package/bundle/components/Link.md +161 -0
- package/bundle/components/List.md +247 -0
- package/bundle/components/ListMaster.md +204 -0
- package/bundle/components/Loader.md +161 -0
- package/bundle/components/LoaderScreen.md +134 -0
- package/bundle/components/Marker.md +127 -0
- package/bundle/components/MarkerStatus.md +148 -0
- package/bundle/components/MediaWidth.md +169 -0
- package/bundle/components/MobileView.md +120 -0
- package/bundle/components/ModalWindow.md +308 -0
- package/bundle/components/MonthYearField.md +168 -0
- package/bundle/components/MultiselectField.md +213 -0
- package/bundle/components/Notification.md +233 -0
- package/bundle/components/NumberField.md +178 -0
- package/bundle/components/OrderedList.md +169 -0
- package/bundle/components/Overlay.md +237 -0
- package/bundle/components/Page.md +246 -0
- package/bundle/components/Pagination.md +298 -0
- package/bundle/components/Portal.md +119 -0
- package/bundle/components/Radio.md +174 -0
- package/bundle/components/Row.md +125 -0
- package/bundle/components/SMSField.md +160 -0
- package/bundle/components/SegmentedControl.md +235 -0
- package/bundle/components/SelectExtendedField.md +322 -0
- package/bundle/components/SelectField.md +213 -0
- package/bundle/components/Skeleton.md +129 -0
- package/bundle/components/Slider.md +157 -0
- package/bundle/components/SliderExtended.md +210 -0
- package/bundle/components/SliderRange.md +160 -0
- package/bundle/components/SmallInput.md +101 -0
- package/bundle/components/Spoiler.md +121 -0
- package/bundle/components/StatusTracker.md +151 -0
- package/bundle/components/Step.md +121 -0
- package/bundle/components/Stepper.md +163 -0
- package/bundle/components/Suggest.md +160 -0
- package/bundle/components/SuggestField.md +157 -0
- package/bundle/components/SwipeableArea.md +144 -0
- package/bundle/components/Table.md +199 -0
- package/bundle/components/Tabs.md +152 -0
- package/bundle/components/TabsExtended.md +273 -0
- package/bundle/components/TabsLine.md +189 -0
- package/bundle/components/Tag.md +135 -0
- package/bundle/components/TagColor.md +121 -0
- package/bundle/components/TagGroup.md +100 -0
- package/bundle/components/TextField.md +152 -0
- package/bundle/components/TextareaField.md +124 -0
- package/bundle/components/ThemeProvider.md +181 -0
- package/bundle/components/Tooltip.md +254 -0
- package/bundle/components/TopOverlay.md +145 -0
- package/bundle/components/TreeView.md +224 -0
- package/bundle/components/Triggers.md +121 -0
- package/bundle/guides/codestyle.md +205 -0
- package/bundle/guides/commits.md +166 -0
- package/bundle/guides/context.md +277 -0
- package/bundle/guides/stories.md +618 -0
- package/bundle/guides/template.md +154 -0
- package/bundle/guides/tests.md +301 -0
- package/bundle/icons.json +4156 -0
- package/bundle/manifest.json +796 -0
- package/bundle/release-notes/1.1.0.mdx +26 -0
- package/bundle/release-notes/1.1.1.mdx +14 -0
- package/bundle/release-notes/1.10.0.mdx +43 -0
- package/bundle/release-notes/1.11.0.mdx +19 -0
- package/bundle/release-notes/1.12.0.mdx +21 -0
- package/bundle/release-notes/1.13.0.mdx +43 -0
- package/bundle/release-notes/1.14.0.mdx +29 -0
- package/bundle/release-notes/1.15.0.mdx +56 -0
- package/bundle/release-notes/1.16.0.mdx +55 -0
- package/bundle/release-notes/1.17.0.mdx +15 -0
- package/bundle/release-notes/1.18.0.mdx +24 -0
- package/bundle/release-notes/1.19.0.mdx +48 -0
- package/bundle/release-notes/1.19.1.mdx +9 -0
- package/bundle/release-notes/1.2.0.mdx +80 -0
- package/bundle/release-notes/1.20.0.mdx +48 -0
- package/bundle/release-notes/1.21.0.mdx +66 -0
- package/bundle/release-notes/1.22.0.mdx +75 -0
- package/bundle/release-notes/1.23.0.mdx +50 -0
- package/bundle/release-notes/1.24.0.mdx +59 -0
- package/bundle/release-notes/1.25.0.mdx +52 -0
- package/bundle/release-notes/1.26.0.mdx +11 -0
- package/bundle/release-notes/1.3.0.mdx +21 -0
- package/bundle/release-notes/1.4.0.mdx +37 -0
- package/bundle/release-notes/1.5.0.mdx +24 -0
- package/bundle/release-notes/1.6.0.mdx +37 -0
- package/bundle/release-notes/1.6.1.mdx +11 -0
- package/bundle/release-notes/1.7.0.mdx +29 -0
- package/bundle/release-notes/1.8.0.mdx +38 -0
- package/bundle/release-notes/1.9.0.mdx +28 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +324 -0
- package/dist/index.js.map +1 -0
- 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 | Создан документ |
|