@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,160 @@
|
|
|
1
|
+
---
|
|
2
|
+
component: Suggest
|
|
3
|
+
category: Form Controls (base)
|
|
4
|
+
figma-node: ""
|
|
5
|
+
figma-file: ""
|
|
6
|
+
related: [SuggestField, SelectField, Dropdown]
|
|
7
|
+
tokens: []
|
|
8
|
+
stories: ""
|
|
9
|
+
version: "1.0"
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Suggest
|
|
13
|
+
|
|
14
|
+
## Назначение
|
|
15
|
+
|
|
16
|
+
Headless-примитив для построения autocomplete/suggest-полей. Управляет
|
|
17
|
+
состоянием `inputValue`, `dropdownOpen`, `activeDescendant`, синхронизацией
|
|
18
|
+
с внешним `value`, обработкой ESC и outside-click. Рендер UI — полностью на
|
|
19
|
+
потребителе через children + Context.
|
|
20
|
+
|
|
21
|
+
Используй когда: нужен кастомный autocomplete поверх базовой логики.
|
|
22
|
+
Для готового UI-компонента — `SuggestField`.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Файловая структура
|
|
27
|
+
|
|
28
|
+
```text
|
|
29
|
+
src/components/Suggest/
|
|
30
|
+
├── Suggest.tsx # forwardRef, корень + Provider
|
|
31
|
+
├── SuggestContext.ts
|
|
32
|
+
├── types.ts # ISuggestProps, ISuggestOption, ISuggestTargetProps...
|
|
33
|
+
├── useSuggest.ts # весь state + колбэки
|
|
34
|
+
├── SuggestMobileDropdownContent.tsx # mobile-вариант списка
|
|
35
|
+
├── index.ts
|
|
36
|
+
└── styles/
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Публичный API
|
|
42
|
+
|
|
43
|
+
### `Suggest<T extends ISuggestOption>` (forwardRef)
|
|
44
|
+
|
|
45
|
+
Generic по типу опции. Корневой `<div>` с `onKeyDown` и ref.
|
|
46
|
+
|
|
47
|
+
| Prop | Тип | Описание |
|
|
48
|
+
|---|---|---|
|
|
49
|
+
| `value` | `T \| undefined` | Выбранная опция. |
|
|
50
|
+
| `options` | `T[]` | Список опций. |
|
|
51
|
+
| `size` | `EComponentSize` | Размер (прокидывается детям). |
|
|
52
|
+
| `onSelect` | `(value: T \| undefined) => void` | Колбэк выбора. Вызывается после `closeDropdown`. |
|
|
53
|
+
| `onFilter` | `(value: string) => void` | Колбэк ввода в input. |
|
|
54
|
+
| `onScrollEnd?` | `() => void` | Вызов при скролле до конца списка (пагинация). |
|
|
55
|
+
| `placeholder?` | `string` | Передаётся в Context. |
|
|
56
|
+
| `noOptionsText?` | `string` | Текст «ничего не найдено». |
|
|
57
|
+
| `loading?` | `boolean` | Флаг загрузки корневого уровня. |
|
|
58
|
+
| `dropdownListLoading?` | `boolean` | Флаг загрузки списка (пагинация). |
|
|
59
|
+
| `tooltipOpen?` | `boolean` | Контролируемое открытие Tooltip (в потребителях). |
|
|
60
|
+
| `clearInputOnFocus?` | `boolean` | Очищать input при focus. |
|
|
61
|
+
| `children` | `ReactNode` | Слои UI (Target/Dropdown потребителя). |
|
|
62
|
+
| `...HTMLAttributes<HTMLDivElement>` | — | Прокидываются на корневой div (кроме `onSelect`). |
|
|
63
|
+
|
|
64
|
+
### `ISuggestOption`
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
{ id: string; label: string; content?: ReactNode; showNotificationIcon?: boolean }
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
`label` используется как текст в inputValue по умолчанию.
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Хук `useSuggest`
|
|
75
|
+
|
|
76
|
+
Вся логика. Выставляет в контекст:
|
|
77
|
+
- `inputValue`, `setInputValue`
|
|
78
|
+
- `activeDescendant`, `setActiveDescendant`
|
|
79
|
+
- `dropdownOpen`, `setDropdownOpen`
|
|
80
|
+
- `dropdownListId` (уникальный `dropdown-list-<n>`)
|
|
81
|
+
- `closeDropdown(newInputValue?)` — закрытие с восстановлением значения из `latestValueRef`
|
|
82
|
+
- `onSelect` — вызывает `closeDropdown(label)` + `props.onSelect`
|
|
83
|
+
- `onFilter` — вызывает `setInputValue` + `props.onFilter`
|
|
84
|
+
- `onKeyDown` — Escape закрывает dropdown (с `stopPropagation`), остальные — прокидывает в props.onKeyDown.
|
|
85
|
+
|
|
86
|
+
### Синхронизация value → inputValue
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
if (value !== prevValue) {
|
|
90
|
+
setPrevValue(value);
|
|
91
|
+
setInputValue(value?.label || "");
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Синхронизация через «derived state pattern» (сравнение в render'е, не в useEffect).
|
|
96
|
+
|
|
97
|
+
### `latestValueRef`
|
|
98
|
+
|
|
99
|
+
`useLayoutEffect` обновляет ref после каждого рендера — чтобы `closeDropdown`
|
|
100
|
+
(создаваемый в useCallback один раз) всегда читал актуальный `value`
|
|
101
|
+
при восстановлении `inputValue`.
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Context (`SuggestContext`)
|
|
106
|
+
|
|
107
|
+
Полное расширение возврата useSuggest + публичные props:
|
|
108
|
+
`value`, `options`, `placeholder`, `noOptionsText`, `loading`, `dropdownListLoading`,
|
|
109
|
+
`tooltipOpen`, `clearInputOnFocus`, `onScrollEnd`, `suggestRef`, `dropdownRef` + state/колбэки из useSuggest.
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Поведение outside-click
|
|
114
|
+
|
|
115
|
+
`useEffect` на `dropdownOpen`: вешает `mousedown`-listener на `document`,
|
|
116
|
+
закрывает dropdown, если клик не попал ни в `suggestRef`, ни в `dropdownRef`.
|
|
117
|
+
|
|
118
|
+
`dropdownRef` — заполняется потребителем (например, `SuggestField` прокидывает ref на Dropdown-контейнер).
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## Инварианты
|
|
123
|
+
|
|
124
|
+
- **`forwardRef<HTMLDivElement>`** + **generic `<T extends ISuggestOption>`** — сохранять оба. Тип результата — кастомный через `as` cast из-за generic'а.
|
|
125
|
+
- **`SuggestContext` — единственный источник состояния** для детей. Не создавать параллельные useState в target/dropdown компонентах потребителя.
|
|
126
|
+
- **ESC закрывает dropdown с `stopPropagation()`** — чтобы не всплывало в `Overlay`/`Modal`.
|
|
127
|
+
- **Синхронизация `value` → `inputValue`** через render-сравнение (`value !== prevValue`). Не переписывать на `useEffect` без обсуждения — появятся «мерцания».
|
|
128
|
+
- **`closeDropdown` использует `latestValueRef`, не `value`** — чтобы closure в listener'ах отдавал актуальное значение.
|
|
129
|
+
- **`dropdownListId` — один на инстанс** (через `uniqueId()` в `useState` initializer).
|
|
130
|
+
- **`onSelect(undefined)`** — допустимо (сбрасывает выбор). Потребитель должен это учесть.
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Accessibility
|
|
135
|
+
|
|
136
|
+
- Компонент не навязывает ARIA — роли (`combobox`, `listbox`, `option`), `aria-controls`, `aria-expanded`, `aria-activedescendant` задаёт потребитель (напр. `SuggestField`).
|
|
137
|
+
- `dropdownListId` доступен через контекст — потребитель должен повесить его на `<ul role="listbox">` и использовать как `aria-controls` на input'е.
|
|
138
|
+
- Escape-handling реализован на корневом div — потребитель не должен дублировать его на input.
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Связанные компоненты
|
|
143
|
+
|
|
144
|
+
- `SuggestField` — готовый UI поверх Suggest.
|
|
145
|
+
- `Dropdown` — используется внутри SuggestField для выпадающего списка.
|
|
146
|
+
- `SelectField` — для статического списка без фильтрации.
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## Stories
|
|
151
|
+
|
|
152
|
+
Отдельных stories нет — демонстрируется через `SuggestField`.
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## История изменений
|
|
157
|
+
|
|
158
|
+
| Дата | Изменение |
|
|
159
|
+
|---|---|
|
|
160
|
+
| 2026-04-18 | Создан документ |
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
---
|
|
2
|
+
component: SuggestField
|
|
3
|
+
category: Form Controls
|
|
4
|
+
figma-node: ""
|
|
5
|
+
figma-file: ""
|
|
6
|
+
related: [Suggest, SelectField, Dropdown, TextField, MobileView, FormFieldInput]
|
|
7
|
+
tokens: []
|
|
8
|
+
stories: stories/SuggestField/SuggestField.stories.tsx
|
|
9
|
+
version: "1.0"
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# SuggestField
|
|
13
|
+
|
|
14
|
+
## Назначение
|
|
15
|
+
|
|
16
|
+
Поле autocomplete: input + dropdown с фильтрацией опций. Адаптивное
|
|
17
|
+
переключение desktop/mobile через `MobileView` — desktop показывает Dropdown,
|
|
18
|
+
mobile — полноэкранный лист с hint. Поддерживает render-функции
|
|
19
|
+
(`renderInput`, `renderDropdown`, `renderDropdownList`, `renderDropdownListItem`)
|
|
20
|
+
для кастомизации.
|
|
21
|
+
|
|
22
|
+
Используй когда: нужен готовый UX autocomplete-поля с label/tooltip/loading/
|
|
23
|
+
noOptionsText. Для headless-логики — `Suggest` (+ свой UI).
|
|
24
|
+
Для статического dropdown без фильтрации — `SelectField`.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Файловая структура
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
src/components/SuggestField/
|
|
32
|
+
├── SuggestField.tsx # корневой — MobileView switcher
|
|
33
|
+
├── types.ts # ISuggestFieldProps, ISuggestFieldOption
|
|
34
|
+
├── desktop/
|
|
35
|
+
│ ├── SuggestFieldDesktop.tsx
|
|
36
|
+
│ ├── SuggestFieldDesktopDropdown.tsx
|
|
37
|
+
│ └── types.ts
|
|
38
|
+
├── mobile/
|
|
39
|
+
│ ├── SuggestFieldMobile.tsx
|
|
40
|
+
│ ├── SuggestFieldMobileDropdown.tsx
|
|
41
|
+
│ ├── SuggestFieldMobileDropdownHint.tsx
|
|
42
|
+
│ └── types.ts
|
|
43
|
+
└── index.ts
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**Примечание:** в `SuggestFieldDesktop.tsx` — TODO «переписать через useSuggest».
|
|
47
|
+
Desktop-вариант дублирует логику из `useSuggest` (свой state). Mobile тоже.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Публичный API
|
|
52
|
+
|
|
53
|
+
### `SuggestField<T extends ISuggestFieldOption>`
|
|
54
|
+
|
|
55
|
+
Generic. Корневой компонент-switcher.
|
|
56
|
+
|
|
57
|
+
Props = `Omit<ITextFieldProps, "onSelect"> & DataAttributes` + поля ниже:
|
|
58
|
+
|
|
59
|
+
| Prop | Тип | Описание |
|
|
60
|
+
|---|---|---|
|
|
61
|
+
| `value` | `T \| undefined` | Выбранная опция. |
|
|
62
|
+
| `options` | `T[]` | Список опций. |
|
|
63
|
+
| `label?` | `string` | Лейбл над полем. |
|
|
64
|
+
| `placeholder?` | `string` | Подсказка в пустом поле. |
|
|
65
|
+
| `tooltipHint` | `string` | Текст Tooltip (обязателен). |
|
|
66
|
+
| `tooltipOpen` | `boolean` | Контроль видимости Tooltip (обязателен). |
|
|
67
|
+
| `loading?` | `boolean` | Общий loading. |
|
|
68
|
+
| `dropdownListLoading?` | `boolean` | Loading хвоста списка (при пагинации). |
|
|
69
|
+
| `clearInputOnFocus?` | `boolean` | Очищать input при focus. |
|
|
70
|
+
| `onSelect` | `(value: T \| undefined) => void` | Колбэк выбора. |
|
|
71
|
+
| `onFilter` | `(value: string) => void` | Колбэк ввода. |
|
|
72
|
+
| `onScrollEnd?` | `() => void` | Пагинация. |
|
|
73
|
+
| `onClear?` | `MouseEventHandler<HTMLButtonElement>` | Колбэк кнопки очистки. |
|
|
74
|
+
| `renderInput?` | `(props: IFormFieldInputProps) => JSX.Element` | Кастомный input. |
|
|
75
|
+
| `inputProps` | `IFormFieldInputProps` | Обязателен (наследовано из `TextField`). |
|
|
76
|
+
|
|
77
|
+
Desktop дополнительно принимает `renderDropdown`, `renderDropdownList`, `renderDropdownListItem`.
|
|
78
|
+
|
|
79
|
+
### `ISuggestFieldOption`
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
{ id: string; label: string; content?: ReactNode; showNotificationIcon?: boolean }
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### `SuggestField.Input`
|
|
86
|
+
|
|
87
|
+
Re-export `FormFieldInput` — для удобства доступа.
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## Поведение
|
|
92
|
+
|
|
93
|
+
### Desktop
|
|
94
|
+
|
|
95
|
+
- Собственный state: `inputValue`, `inputFocused`, `dropdownOpen`, `ignoreAutoOpen`, `activeDescendant`, `dropdownListId`.
|
|
96
|
+
- `ignoreAutoOpen` — предотвращает немедленное переоткрытие dropdown сразу после выбора (иначе селектнутый item → focus остаётся на input → dropdown открывается снова).
|
|
97
|
+
- Синхронизация `value?.id !== prevValue?.id` → `setInputValue(value?.label)`.
|
|
98
|
+
- `clearInputOnFocus` + focus с непустым input → `setInputValue("")` + `onFilter("")`.
|
|
99
|
+
- `activeDescendant` — id подсвеченного элемента списка (для `aria-activedescendant`).
|
|
100
|
+
|
|
101
|
+
### Mobile (`SuggestFieldMobile`)
|
|
102
|
+
|
|
103
|
+
- Открывает полноэкранный лист с input'ом и списком.
|
|
104
|
+
- `SuggestFieldMobileDropdownHint` — текст-подсказка над списком.
|
|
105
|
+
- Рендерится только внутри `MobileView` (viewport <= SM).
|
|
106
|
+
|
|
107
|
+
### `MobileView`
|
|
108
|
+
|
|
109
|
+
Корневой `SuggestField.tsx` рендерит `<MobileView fallback={SuggestFieldDesktop}>SuggestFieldMobile</MobileView>` — выбор по media query.
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Инварианты
|
|
114
|
+
|
|
115
|
+
- **Generic `<T extends ISuggestFieldOption>`** — сохранять. `SuggestField.Input = FormFieldInput`.
|
|
116
|
+
- **`tooltipHint` и `tooltipOpen` — обязательные** (не опциональные, как может казаться). Tooltip — часть UX.
|
|
117
|
+
- **Синхронизация `value` → `inputValue`** через render-сравнение (по `id`, не по ссылке).
|
|
118
|
+
- **`inputProps` обязателен** (прокидывается в `FormFieldInput`) — наследство из `TextField`. Не default'ить на `{}` без причины.
|
|
119
|
+
- **`onSelect(undefined)`** допустимо (сбрасывает).
|
|
120
|
+
- **Два разных state-дерева** (desktop и mobile) — не объединять без refactor'а под `useSuggest`.
|
|
121
|
+
- **`MobileView` определяет ветку по media-query** — не по прямому props. Для force desktop/mobile в тестах — имитировать viewport.
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## Accessibility
|
|
126
|
+
|
|
127
|
+
- `role="combobox"` / `aria-controls` / `aria-expanded` / `aria-activedescendant` проставляются в Desktop-реализации (`SuggestFieldDesktop`) на input'е.
|
|
128
|
+
- `dropdownListId` передаётся в `DropdownList` (`role="listbox"`) и используется как `aria-controls`.
|
|
129
|
+
- Escape закрывает dropdown (`isKey(event.key, "ESCAPE")`).
|
|
130
|
+
- Mobile: полноэкранный лист с явным focus management на input.
|
|
131
|
+
- `onClear` — опционально; если передан, рисуется `FormFieldClear` кнопка с осмысленной ariaLabel (переопределяется через `inputProps`).
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## Связанные компоненты
|
|
136
|
+
|
|
137
|
+
- `Suggest` — headless-основа (TODO: SuggestField должен переехать на него).
|
|
138
|
+
- `SelectField` — статический dropdown.
|
|
139
|
+
- `Dropdown` / `DropdownList` / `DropdownListItem` — UI списка.
|
|
140
|
+
- `TextField` — родительский тип props.
|
|
141
|
+
- `MobileView` — адаптивная обёртка.
|
|
142
|
+
- `FormFieldInput`, `FormFieldClear`, `Tooltip`, `LoaderSmall` — внутренние зависимости.
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Stories
|
|
147
|
+
|
|
148
|
+
Основные истории: `stories/SuggestField/SuggestField.stories.tsx`
|
|
149
|
+
Примеры: `stories/SuggestField/examples/`
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## История изменений
|
|
154
|
+
|
|
155
|
+
| Дата | Изменение |
|
|
156
|
+
|---|---|
|
|
157
|
+
| 2026-04-18 | Создан документ |
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
---
|
|
2
|
+
component: SwipeableArea
|
|
3
|
+
category: Mobile / Layout
|
|
4
|
+
figma-node: ""
|
|
5
|
+
figma-file: ""
|
|
6
|
+
related: [MobileView]
|
|
7
|
+
tokens: []
|
|
8
|
+
stories: ""
|
|
9
|
+
version: "1.0"
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# SwipeableArea
|
|
13
|
+
|
|
14
|
+
## Назначение
|
|
15
|
+
|
|
16
|
+
Мобильный контейнер с поддержкой horizontal-swipe: при свайпе влево открывается
|
|
17
|
+
`rightSwipeableArea` (обычно — action-панель), при свайпе вправо —
|
|
18
|
+
`leftSwipeableArea`. Основной контент смещается `transform: translateX`.
|
|
19
|
+
Работает только через touch-события (нет mouse drag).
|
|
20
|
+
|
|
21
|
+
Используй когда: нужен iOS-подобный swipe-to-reveal в списке (удалить/
|
|
22
|
+
архивировать и т.п.) на мобильных устройствах.
|
|
23
|
+
Не используй когда: нужен desktop drag — `SwipeableArea` не обрабатывает
|
|
24
|
+
mouse events.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Файловая структура
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
src/components/SwipeableArea/
|
|
32
|
+
├── SwipeableArea.tsx # forwardRef<ISwipeableAreaRef, ISwipeableAreaProps>
|
|
33
|
+
├── index.ts
|
|
34
|
+
└── styles/SwipeableArea.module.less
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Варианты и props
|
|
40
|
+
|
|
41
|
+
### `ISwipeableAreaProps`
|
|
42
|
+
|
|
43
|
+
| Prop | Тип | Описание |
|
|
44
|
+
|---|---|---|
|
|
45
|
+
| `children` | `ReactNode` | Основной контент карточки. |
|
|
46
|
+
| `leftSwipeableArea?` | `ReactNode` | Контент, появляющийся при свайпе вправо. Если не задан — свайп вправо игнорируется. |
|
|
47
|
+
| `rightSwipeableArea?` | `ReactNode` | Контент, появляющийся при свайпе влево. Если не задан — свайп влево игнорируется. |
|
|
48
|
+
| `...HTMLAttributes<HTMLDivElement>` | — | Прокидываются на корневой div. |
|
|
49
|
+
|
|
50
|
+
### `ISwipeableAreaRef` (imperative API)
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
interface ISwipeableAreaRef {
|
|
54
|
+
closeSwipe(): void; // закрыть любую открытую панель
|
|
55
|
+
swipeLeft(): void; // открыть rightSwipeableArea (свайп влево)
|
|
56
|
+
swipeRight(): void; // открыть leftSwipeableArea (свайп вправо)
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Доступ через `React.useRef<ISwipeableAreaRef>` + `ref={...}`.
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Логика
|
|
65
|
+
|
|
66
|
+
### Константы
|
|
67
|
+
|
|
68
|
+
| Имя | Значение | Назначение |
|
|
69
|
+
|---|---|---|
|
|
70
|
+
| `SWIPE_MIN_DISTANCE` | `24` (px) | Порог, ниже которого свайп считается «недотянутым» и контент возвращается в исходное положение. |
|
|
71
|
+
|
|
72
|
+
### Определение направления (scroll vs swipe)
|
|
73
|
+
|
|
74
|
+
При первом `touchmove`:
|
|
75
|
+
- `|deltaY| > |deltaX|` → `dragType = vertical` (скролл), свайп не обрабатывается.
|
|
76
|
+
- иначе → `horizontal` (свайп), контент двигается.
|
|
77
|
+
|
|
78
|
+
При отпускании (`touchend` на document) — сброс `dragType`.
|
|
79
|
+
|
|
80
|
+
### Завершение свайпа (`handleSwipe`)
|
|
81
|
+
|
|
82
|
+
Запускается в `useEffect` при `dragType → undefined && contentTranslateX !== start`.
|
|
83
|
+
Логика зависит от начального состояния:
|
|
84
|
+
- Было закрыто, сдвиг > порога → открыть соответствующую панель (translateX = ±width).
|
|
85
|
+
- Было закрыто, сдвиг < порога → вернуть к 0.
|
|
86
|
+
- Было открыто — если сдвиг в сторону закрытия превышает порог → закрыть, иначе — вернуть к открытому состоянию.
|
|
87
|
+
|
|
88
|
+
### Opacity side-панелей
|
|
89
|
+
|
|
90
|
+
`opacity = |contentTranslateX| / panelWidth` — плавное появление.
|
|
91
|
+
|
|
92
|
+
### Outside-touch
|
|
93
|
+
|
|
94
|
+
`document.addEventListener("touchstart")` закрывает swipe при tap'е вне контейнера.
|
|
95
|
+
|
|
96
|
+
### Анимация
|
|
97
|
+
|
|
98
|
+
CSS-класс `swipeAnimationFinish` (`animating = true`) → CSS transition.
|
|
99
|
+
`animating = false` в `onTransitionEnd`.
|
|
100
|
+
|
|
101
|
+
Во время горизонтального drag:
|
|
102
|
+
- `disableScroll` класс → не скроллится страница.
|
|
103
|
+
- `disablePointerEvents` класс → элементы внутри контента не реагируют на клик.
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## Инварианты
|
|
108
|
+
|
|
109
|
+
- **`forwardRef<ISwipeableAreaRef>`** — ref отдаёт imperative API, НЕ DOM-элемент. Это нестандартно, не менять без миграции потребителей.
|
|
110
|
+
- **Только touch-события** — mouse drag не поддерживается намеренно (компонент только для mobile).
|
|
111
|
+
- **`SWIPE_MIN_DISTANCE = 24`** — менять с проверкой UX на реальных устройствах.
|
|
112
|
+
- **`dragType === vertical`** → свайп игнорируется до конца жеста (пока пользователь не отпустит палец). Это предотвращает «дребезг» при диагональных движениях.
|
|
113
|
+
- **`leftSwipeableArea` / `rightSwipeableArea` рендерятся ТОЛЬКО если переданы** (`typeof ... !== "undefined"`) — не оставлять пустые div'ы.
|
|
114
|
+
- **`useImperativeHandle` переопределяет ref-форму** — потребитель не получит HTMLDivElement.
|
|
115
|
+
- **Outside-tap закрывает swipe** (listener на document) — не отключать, это часть UX.
|
|
116
|
+
- **`disablePointerEvents` активен, пока `contentTranslateX !== 0`** — дети не ловят клики при открытой side-панели. Взаимодействие идёт через кнопки в самой side-панели.
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## Accessibility
|
|
121
|
+
|
|
122
|
+
- Компонент не добавляет ARIA — это чисто-тактильный жест.
|
|
123
|
+
- Для screen-reader пользователей side-панели должны быть доступны альтернативным способом (обычно — через action button в самом контенте, `overlayMenu` и т.п.).
|
|
124
|
+
- Imperative API (`swipeLeft`/`swipeRight`) — используется для программного открытия (например, onboarding-туториал).
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## Связанные компоненты
|
|
129
|
+
|
|
130
|
+
- `MobileView` — оборачивает SwipeableArea для условного рендера только на мобильных viewport'ах.
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Stories
|
|
135
|
+
|
|
136
|
+
Отдельных stories нет (mobile-only компонент, тестируется в составе mobile-экранов).
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## История изменений
|
|
141
|
+
|
|
142
|
+
| Дата | Изменение |
|
|
143
|
+
|---|---|
|
|
144
|
+
| 2026-04-18 | Создан документ |
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
---
|
|
2
|
+
component: Table
|
|
3
|
+
category: Data Display
|
|
4
|
+
figma-node: ""
|
|
5
|
+
figma-file: ""
|
|
6
|
+
related: [List, ListSortable, Pagination, FilterPanel]
|
|
7
|
+
tokens: []
|
|
8
|
+
stories: stories/Table/Table.stories.tsx
|
|
9
|
+
version: "1.0"
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Table (MasterTable)
|
|
13
|
+
|
|
14
|
+
## Назначение
|
|
15
|
+
|
|
16
|
+
Набор компонентов для построения таблицы данных: `MasterTable` — корневая
|
|
17
|
+
обёртка с контекстом, внутри рендерятся панели (`FilterPanel`, `TabsLinePanel`,
|
|
18
|
+
`PaginationPanel`), сама таблица (`TableBasic`), настройки колонок
|
|
19
|
+
(`TableBasicSettings`), футер (`TableFooter`). `MasterTable` — композитный,
|
|
20
|
+
дочерние компоненты доступны и как самостоятельные (`export { TableBasic }`
|
|
21
|
+
и т.д.).
|
|
22
|
+
|
|
23
|
+
Используй когда: нужен полноценный data-grid с фильтрами, сортировкой и пагинацией.
|
|
24
|
+
Для простой таблицы без панелей — `MasterTable.TableBasic` в одиночку.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Файловая структура
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
src/components/Table/
|
|
32
|
+
├── MasterTable.tsx # Root FC + Provider
|
|
33
|
+
├── MasterTableContext.ts # { columns, loading, setColumns }
|
|
34
|
+
├── FilterPanel.tsx
|
|
35
|
+
├── TabsLinePanel.tsx
|
|
36
|
+
├── TabsLinePanelLinks.tsx
|
|
37
|
+
├── PaginationPanel.tsx
|
|
38
|
+
├── NoColumns.tsx
|
|
39
|
+
├── TableBasic/
|
|
40
|
+
│ ├── TableBasic.tsx
|
|
41
|
+
│ ├── enums.ts # EOrderDirection, EHorizontalAlign, EVerticalAlign, ECellType
|
|
42
|
+
│ ├── types.ts # ITableBasicColumn, ITableBasicRow, ITableBasicProps, IMasterTableProps...
|
|
43
|
+
│ ├── components/ # TableBasicHeader, TableBasicBody, ...
|
|
44
|
+
│ └── styles/
|
|
45
|
+
├── TableBasicSettings/
|
|
46
|
+
│ ├── TableBasicSettings.tsx
|
|
47
|
+
│ └── components/ColumnSettings/
|
|
48
|
+
├── TableFooter/
|
|
49
|
+
├── utils.ts
|
|
50
|
+
├── styles/
|
|
51
|
+
└── index.ts
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Композиция
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
<MasterTable loading={loading}>
|
|
60
|
+
<MasterTable.FilterPanel>...</MasterTable.FilterPanel>
|
|
61
|
+
<MasterTable.TabsLinePanel>...</MasterTable.TabsLinePanel>
|
|
62
|
+
<MasterTable.TableBasic
|
|
63
|
+
columns={columns}
|
|
64
|
+
data={rows}
|
|
65
|
+
renderNoData={() => <EmptyView ... />}
|
|
66
|
+
onOrderBy={handleOrderBy}
|
|
67
|
+
/>
|
|
68
|
+
<MasterTable.PaginationPanel>
|
|
69
|
+
<Pagination ... />
|
|
70
|
+
</MasterTable.PaginationPanel>
|
|
71
|
+
<MasterTable.TableFooter>...</MasterTable.TableFooter>
|
|
72
|
+
<MasterTable.TableBasicSettings linkTitle="Настройки">
|
|
73
|
+
<ColumnSettings>{...}</ColumnSettings>
|
|
74
|
+
</MasterTable.TableBasicSettings>
|
|
75
|
+
</MasterTable>
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Все подкомпоненты также экспортируются напрямую: `TableBasic`, `TableFooter`,
|
|
79
|
+
`FilterPanel`, `TabsLinePanel`, `PaginationPanel`, `TableBasicSettings`,
|
|
80
|
+
`ColumnSettings`, `NoColumns`.
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Ключевые типы
|
|
85
|
+
|
|
86
|
+
### `IMasterTableProps`
|
|
87
|
+
|
|
88
|
+
| Prop | Тип | Default | Описание |
|
|
89
|
+
|---|---|---|---|
|
|
90
|
+
| `loading?` | `boolean` | `false` | Прокидывается в Context → `TableBasic` рендерит loader-оверлей. |
|
|
91
|
+
| `...HTMLAttributes<HTMLDivElement>` | — | — | На корневой div. |
|
|
92
|
+
|
|
93
|
+
### `ITableBasicProps`
|
|
94
|
+
|
|
95
|
+
| Prop | Тип | Описание |
|
|
96
|
+
|---|---|---|
|
|
97
|
+
| `columns` | `ITableBasicColumn[]` | Колонки. Запись в MasterTableContext через `useEffect` (сравнение по `isEqual`). |
|
|
98
|
+
| `data` | `ITableBasicRow[]` | Строки. Пустой массив → рендерится `renderNoData()`. |
|
|
99
|
+
| `renderNoData` | `() => JSX.Element` | **Обязателен**. Обычно `<EmptyView>`. |
|
|
100
|
+
| `renderNoColumns?` | `() => ReactNode` | Если ВСЕ columns имеют `hidden: true`. |
|
|
101
|
+
| `onOrderBy?` | `(order: ISortOrder) => void` | Колбэк сортировки (из header'а). |
|
|
102
|
+
| `onClickRow?` | `(rowKey: string) => void` | Клик по строке. |
|
|
103
|
+
| `highlightRowOnHover?` | `boolean` | Подсветка hover. |
|
|
104
|
+
| `headless?` | `boolean` | Скрыть header. |
|
|
105
|
+
| `...HTMLAttributes<HTMLTableElement>` | — | На `<table>`. |
|
|
106
|
+
|
|
107
|
+
### `ITableBasicColumn`
|
|
108
|
+
|
|
109
|
+
Поля: `fieldKey` (id колонки и sort key), `label`, `title`, `orderDirection`,
|
|
110
|
+
`horizontalAlign`, `verticalAlign`, `cellType` (`TEXT`/`COMPONENTS`/`CHECKBOX`),
|
|
111
|
+
`width`, `hidden`, `renderCell`, `dataAttributes`, `ariaAttributes`.
|
|
112
|
+
|
|
113
|
+
### `ITableBasicRow`
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
{
|
|
117
|
+
rowKey: string;
|
|
118
|
+
rowData: any; // TODO — типизация не зафиксирована
|
|
119
|
+
rowLayout?: Record<string, { rowSpan?; colSpan? }>;
|
|
120
|
+
selected?: boolean;
|
|
121
|
+
dataAttributes?; ariaAttributes?;
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Enums (`TableBasic/enums.ts`)
|
|
126
|
+
|
|
127
|
+
- `EOrderDirection`: `NONE` / `ASC` / `DESC`
|
|
128
|
+
- `EHorizontalAlign`: `LEFT` / `RIGHT` / `CENTER`
|
|
129
|
+
- `EVerticalAlign`: `BASELINE` / `SUB` / `SUPER` / `TEXT_TOP` / `TEXT_BOTTOM` / `MIDDLE` / `TOP` / `BOTTOM`
|
|
130
|
+
- `ECellType`: `TEXT` / `COMPONENTS` / `CHECKBOX`
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Контекст (`MasterTableContext`)
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
{ columns: ITableBasicColumn[]; loading: boolean; setColumns: (cols) => void; }
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
`TableBasic` пишет свои columns в Context через `useEffect`; потребители
|
|
141
|
+
(`TableBasicSettings` / `ColumnSettings`) читают их для настройки видимости/
|
|
142
|
+
порядка колонок.
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Loading
|
|
147
|
+
|
|
148
|
+
`MasterTable` передаёт `loading` в Context. `TableBasic` рендерит:
|
|
149
|
+
- `loading && empty` → overlay + LoaderMiddle поверх `renderNoData`
|
|
150
|
+
- `!loading && empty` → `renderNoData()`
|
|
151
|
+
- `loading && !empty` → `LoaderScreen` (`middle` type) поверх реальной таблицы
|
|
152
|
+
- иначе — таблица без loader'а
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## Инварианты
|
|
157
|
+
|
|
158
|
+
- **`renderNoData` обязателен** — компонент намеренно не добавляет дефолтный empty-state.
|
|
159
|
+
- **`columns` сравниваются по `isEqual`** (deep) перед записью в Context — не мутировать массив in-place, менять ссылку.
|
|
160
|
+
- **`MasterTable` — FC + композиция через присваивания** (`MasterTable.TableBasic = TableBasic` и т.д.).
|
|
161
|
+
- **`TableBasic` — не forwardRef** (деструктурируется, ref попадает в `...htmlTableAttributes`, но не прокидывается явно).
|
|
162
|
+
- **Все enum'ы (`EOrderDirection`, `EHorizontalAlign`, `EVerticalAlign`, `ECellType`)** — часть публичного API.
|
|
163
|
+
- **`rowData: any`** — намеренно (`TODO` в коде). Не типизировать без координации.
|
|
164
|
+
- **`TableBasicSettings` принимает `linkTitle: string`** — обязательный.
|
|
165
|
+
- **Все компоненты из `index.ts` экспортируются отдельно** — используются напрямую в legacy-коде.
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## Accessibility
|
|
170
|
+
|
|
171
|
+
- `<table>` — семантический. Заголовок через `<thead>` / `<th>` в `TableBasicHeader`.
|
|
172
|
+
- `aria-sort` на `<th>` выставляется `TableBasicHeader` по `orderDirection`.
|
|
173
|
+
- `ariaAttributes` на колонке и строке — потребитель может добавить custom aria.
|
|
174
|
+
- Footer (loading/empty) — чисто визуальный, без aria-live (добавить при необходимости в потребителе).
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## Связанные компоненты
|
|
179
|
+
|
|
180
|
+
- `Pagination` / `Ellipsis` — используются внутри `PaginationPanel`.
|
|
181
|
+
- `TabsLine` — внутри `TabsLinePanel`.
|
|
182
|
+
- `ButtonDropdownExtended` — основа `TableBasicSettings`.
|
|
183
|
+
- `List` / `ListSortable` — основа `ColumnSettings` для управления колонками.
|
|
184
|
+
- `EmptyView` — типичный `renderNoData`.
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## Stories
|
|
189
|
+
|
|
190
|
+
Основные истории: `stories/Table/Table.stories.tsx`
|
|
191
|
+
Примеры: `stories/Table/examples/`
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## История изменений
|
|
196
|
+
|
|
197
|
+
| Дата | Изменение |
|
|
198
|
+
|---|---|
|
|
199
|
+
| 2026-04-18 | Создан документ |
|