@sberbusiness/triplex-next-mcp-server 1.43.0-beta → 1.45.0-beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/README.md +7 -6
  2. package/bundle/components/AlertContext.md +11 -8
  3. package/bundle/components/AlertProcess.md +16 -12
  4. package/bundle/components/Badge.md +6 -3
  5. package/bundle/components/BodyPage.md +72 -4
  6. package/bundle/components/Button.md +46 -43
  7. package/bundle/components/ButtonDropdownExtended.md +3 -2
  8. package/bundle/components/ButtonIcon.md +7 -4
  9. package/bundle/components/Calendar.md +62 -59
  10. package/bundle/components/Caption.md +26 -23
  11. package/bundle/components/Checkbox.md +32 -29
  12. package/bundle/components/CheckboxTreeExtended.md +9 -5
  13. package/bundle/components/Chip.md +44 -41
  14. package/bundle/components/CodeText.md +26 -23
  15. package/bundle/components/Col.md +1 -2
  16. package/bundle/components/CollapsibleTree.md +8 -5
  17. package/bundle/components/CollapsibleTreeExtended.md +6 -1
  18. package/bundle/components/DatePickerExtended.md +409 -262
  19. package/bundle/components/Dropdown.md +33 -28
  20. package/bundle/components/EmptyView.md +12 -5
  21. package/bundle/components/ExpandAnimation.md +263 -218
  22. package/bundle/components/Footer.md +235 -278
  23. package/bundle/components/FooterPage.md +6 -2
  24. package/bundle/components/FormField.md +44 -41
  25. package/bundle/components/FormGroup.md +192 -52
  26. package/bundle/components/Header.md +491 -211
  27. package/bundle/components/HeaderPage.md +6 -2
  28. package/bundle/components/ImageGallery.md +33 -34
  29. package/bundle/components/ImageGalleryExtended.md +33 -34
  30. package/bundle/components/Island.md +52 -14
  31. package/bundle/components/LightBox.md +25 -18
  32. package/bundle/components/Link.md +211 -96
  33. package/bundle/components/ListActionItem.md +7 -4
  34. package/bundle/components/ListItem.md +11 -8
  35. package/bundle/components/ListItemControlsButton.md +16 -13
  36. package/bundle/components/ListItemControlsButtonDropdown.md +9 -6
  37. package/bundle/components/ListItemSelectable.md +7 -4
  38. package/bundle/components/ListMasterFooter.md +8 -5
  39. package/bundle/components/ListMasterHeader.md +8 -5
  40. package/bundle/components/ListSortableItem.md +11 -8
  41. package/bundle/components/ListTableItem.md +7 -4
  42. package/bundle/components/LoaderMiddle.md +7 -4
  43. package/bundle/components/LoaderScreen.md +11 -8
  44. package/bundle/components/LoaderSmall.md +7 -4
  45. package/bundle/components/Marker.md +184 -69
  46. package/bundle/components/MarkerStatus.md +206 -88
  47. package/bundle/components/MaskedField.md +2 -4
  48. package/bundle/components/MasterTable.md +602 -0
  49. package/bundle/components/ModalWindow.md +7 -4
  50. package/bundle/components/MultiselectField.md +2183 -132
  51. package/bundle/components/Notification.md +12 -9
  52. package/bundle/components/Overlay.md +33 -5
  53. package/bundle/components/Page.md +2 -2
  54. package/bundle/components/Pagination.md +27 -21
  55. package/bundle/components/Row.md +1 -1
  56. package/bundle/components/SelectExtendedField.md +3 -3
  57. package/bundle/components/SelectField.md +466 -148
  58. package/bundle/components/SliderExtended.md +39 -36
  59. package/bundle/components/SmallInput.md +194 -51
  60. package/bundle/components/Suggest.md +535 -99
  61. package/bundle/components/SwipeableArea.md +428 -79
  62. package/bundle/components/TableBasic.md +2302 -0
  63. package/bundle/components/TabsExtended.md +24 -21
  64. package/bundle/components/Tag.md +263 -78
  65. package/bundle/components/Text.md +28 -25
  66. package/bundle/components/TextField.md +3 -4
  67. package/bundle/components/ThemeProvider.md +50 -1
  68. package/bundle/components/Title.md +26 -23
  69. package/bundle/components/Tooltip.md +24 -21
  70. package/bundle/components/TopOverlay.md +368 -89
  71. package/bundle/components/TreeView.md +343 -166
  72. package/bundle/components/TreeViewNode.md +436 -0
  73. package/bundle/guides/codestyle.md +3 -0
  74. package/bundle/guides/commits.md +106 -26
  75. package/bundle/guides/context.md +84 -12
  76. package/bundle/guides/guidelines.md +321 -0
  77. package/bundle/guides/template.md +22 -12
  78. package/bundle/manifest.json +54 -20
  79. package/bundle/release-notes/1.44.0.mdx +113 -0
  80. package/bundle/release-notes/1.45.0.mdx +124 -0
  81. package/bundle/release-notes/1.46.0.mdx +7 -0
  82. package/bundle/tokens.json +6362 -0
  83. package/dist/index.js +177 -28
  84. package/dist/index.js.map +1 -1
  85. package/package.json +1 -1
@@ -1,11 +1,9 @@
1
1
  ---
2
2
  component: Suggest
3
- category: Form Controls (base)
4
- figma-node: ""
5
- figma-file: ""
6
- related: [SuggestField, SelectField, Dropdown]
3
+ category: Suggest
4
+ related: [SuggestField, Chip, Dropdown]
7
5
  tokens: []
8
- stories: ""
6
+ stories: stories/Suggest/Suggest.stories.tsx
9
7
  version: "1.0"
10
8
  ---
11
9
 
@@ -13,143 +11,171 @@ version: "1.0"
13
11
 
14
12
  ## Назначение
15
13
 
16
- Headless-примитив для построения autocomplete/suggest-полей. Управляет
17
- состоянием `inputValue`, `dropdownOpen`, `activeDescendant`, синхронизацией
18
- с внешним `value`, обработкой ESC и outside-click. Рендер UI — полностью на
19
- потребителе через children + Context.
14
+ Headless-основа выпадающего списка с фильтрацией по введённому значению. Собственной
15
+ разметки не добавляет: рендерит `<div>`-обёртку и раздаёт потомкам через `SuggestContext`
16
+ значение поля ввода, видимость выпадающего списка и обработчики выбора, фильтрации и
17
+ клавиатуры. Управляющий элемент (поле ввода, чипс, кнопка) и сам выпадающий список пишет
18
+ потребитель — хук `useSuggestContext` отдаёт всё нужное состояние.
20
19
 
21
- Используй когда: нужен кастомный autocomplete поверх базовой логики.
22
- Для готового UI-компонента — `SuggestField`.
20
+ Внутри библиотеки на этой основе построен `ChipSuggest`. `SuggestField` решает ту же
21
+ задачу, но собственной реализацией состояния — на `Suggest` он пока не переведён
22
+ (в `SuggestField.tsx` стоит `TODO: Переписать через useSuggest`).
23
23
 
24
- ---
24
+ Используй когда: нужен suggest с нестандартным управляющим элементом или нестандартным
25
+ выпадающим списком, а готовые `SuggestField` / `ChipSuggest` не подходят по разметке.
25
26
 
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
- ```
27
+ Не используй когда:
28
+ - Нужно обычное поле-suggest с лейблом, статусами и кнопкой очистки — возьми
29
+ `SuggestField`, он уже собран.
30
+ - Нужен suggest-фильтр в ряду чипсов — возьми `ChipSuggest`.
31
+ - Список не фильтруется вводом — возьми `SelectField` или `Dropdown` напрямую.
38
32
 
39
33
  ---
40
34
 
41
- ## Публичный API
35
+ ## Варианты и props
42
36
 
43
- ### `Suggest<T extends ISuggestOption>` (forwardRef)
44
-
45
- Generic по типу опции. Корневой `<div>` с `onKeyDown` и ref.
37
+ ### Обязательные props
46
38
 
47
39
  | Prop | Тип | Описание |
48
40
  |---|---|---|
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
- ```
41
+ | `value` | `T \| undefined` | Выбранная опция. Контролируемое значение: при его смене поле ввода перезаписывается на `value.label` |
42
+ | `options` | `T[]` | Список опций. Фильтрацию делает потребитель в `onFilter` — компонент список не фильтрует |
43
+ | `size` | `EComponentSize` | Размер. Suggest его **не отображает и не кладёт в контекст** — свойство существует, чтобы не попасть в DOM атрибутом; раздавай размер управляющему элементу и списку сам |
44
+ | `onSelect` | `(value: T \| undefined) => void` | Вызывается после выбора опции. Перед вызовом список закрывается, а в поле ввода подставляется `label` выбранной опции |
45
+ | `onFilter` | `(value: string) => void` | Вызывается на каждое изменение поля ввода. Ответ — обновлённый `options` |
69
46
 
70
- `label` используется как текст в inputValue по умолчанию.
47
+ ### Опциональные props
71
48
 
72
- ---
49
+ | Prop | Тип | По умолчанию | Описание |
50
+ |---|---|---|---|
51
+ | `placeholder` | `string` | — | Текст подсказки. Только раздаётся через контекст, компонент его не рендерит |
52
+ | `noOptionsText` | `string` | — | Текст при пустом `options`. Рендерится в мобильном наполнении, в десктопном — потребителем |
53
+ | `loading` | `boolean` | — | Флаг загрузки. В мобильном наполнении рисует лоадер в шапке, в десктопном — на усмотрение потребителя |
54
+ | `dropdownListLoading` | `boolean` | — | Флаг догрузки списка: лоадер под уже загруженными опциями |
55
+ | `tooltipOpen` | `boolean` | — | Управление видимостью Tooltip у потребителя. Suggest только раздаёт значение через контекст |
56
+ | `clearInputOnFocus` | `boolean` | — | Очищать поле ввода при получении фокуса. Обработчик `onFocus` пишет потребитель — Suggest ничего не слушает |
57
+ | `onScrollEnd` | `() => void` | — | Доступные опции закончились при скролле списка. В мобильном наполнении вызывается автоматически |
58
+ | `onKeyDown` | `React.KeyboardEventHandler` | — | Обработчик клавиатуры на корневом `<div>`. Вызывается после внутренней обработки `Escape` |
73
59
 
74
- ## Хук `useSuggest`
60
+ Все остальные `React.HTMLAttributes<HTMLDivElement>` (кроме `onSelect`) попадают на
61
+ корневой `<div>`: `className`, `id`, `data-*`, `aria-*`, `style`.
75
62
 
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.
63
+ ### Что даёт `useSuggestContext`
85
64
 
86
- ### Синхронизация value → inputValue
65
+ Помимо перечисленных выше props потребитель получает состояние, которого нет во внешнем API:
87
66
 
88
- ```
89
- if (value !== prevValue) {
90
- setPrevValue(value);
91
- setInputValue(value?.label || "");
92
- }
93
- ```
67
+ | Поле | Описание |
68
+ |---|---|
69
+ | `inputValue` / `setInputValue` | Текущий текст поля ввода. `setInputValue` не вызывает `onFilter` |
70
+ | `dropdownOpen` / `setDropdownOpen` | Видимость списка. `setDropdownOpen(false)` не сбрасывает поле ввода |
71
+ | `closeDropdown(newInputValue?)` | Закрывает список и возвращает в поле `newInputValue` либо `label` текущего `value` |
72
+ | `activeDescendant` / `setActiveDescendant` | Идентификатор элемента, выделенного клавиатурой, для `aria-activedescendant`. Библиотека его не выставляет — клавиатурную навигацию по списку реализует потребитель. `setActiveDescendant` приходит в контекст из `useSuggest` в рантайме, но в типе значения контекста не объявлен — типизация поля меняет публичный тип `SuggestContext`, решение за мейнтейнером |
73
+ | `dropdownListId` | Сгенерированный `dropdown-list-{n}` для `id` списка и `aria-controls` на управляющем элементе |
74
+ | `suggestRef` | Ссылка на корневой `<div>`. Годится как `targetRef` для `Dropdown` |
75
+ | `dropdownRef` | Ссылку **обязан заполнить потребитель**, повесив её на выпадающий список. По ней Suggest отличает клик внутри списка от клика вне компонента |
76
+ | `onSelect` / `onFilter` | Обёртки над одноимёнными props: дополнительно обновляют `inputValue` и закрывают список |
94
77
 
95
- Синхронизация через «derived state pattern» (сравнение в render'е, не в useEffect).
78
+ ### Опция (`ISuggestOption`)
96
79
 
97
- ### `latestValueRef`
80
+ `id` (уникальный, используется как ключ и для сравнения с `value`), `label` (текст в поле
81
+ ввода и в списке), `content` (кастомная разметка опции в списке вместо `label`),
82
+ `showNotificationIcon` (значок новых уведомлений).
98
83
 
99
- `useLayoutEffect` обновляет ref после каждого рендера — чтобы `closeDropdown`
100
- (создаваемый в useCallback один раз) всегда читал актуальный `value`
101
- при восстановлении `inputValue`.
84
+ Тип опции расширяется дженериком: `<Suggest<IMyOption> ...>` протаскивает `IMyOption`
85
+ в `value`, `options` и `onSelect`.
102
86
 
103
87
  ---
104
88
 
105
- ## Context (`SuggestContext`)
89
+ ## Дизайн-токены
106
90
 
107
- Полное расширение возврата useSuggest + публичные props:
108
- `value`, `options`, `placeholder`, `noOptionsText`, `loading`, `dropdownListLoading`,
109
- `tooltipOpen`, `clearInputOnFocus`, `onScrollEnd`, `suggestRef`, `dropdownRef` + state/колбэки из useSuggest.
91
+ Собственных токенов нет: единственный стиль компонента —
92
+ `styles/SuggestDropdown.module.less` — задаёт высоту и отступы мобильного наполнения
93
+ фиксированными значениями. Внешний вид определяют компоненты, которыми потребитель
94
+ собирает управляющий элемент и список (`TextField`, `Chip`, `Dropdown`), — их токены
95
+ переопределяются через `ThemeProvider` в соответствующих AI.md.
110
96
 
111
97
  ---
112
98
 
113
- ## Поведение outside-click
114
-
115
- `useEffect` на `dropdownOpen`: вешает `mousedown`-listener на `document`,
116
- закрывает dropdown, если клик не попал ни в `suggestRef`, ни в `dropdownRef`.
99
+ ## Инварианты
117
100
 
118
- `dropdownRef` — заполняется потребителем (например, `SuggestField` прокидывает ref на Dropdown-контейнер).
101
+ - `forwardRef` на `Suggest` — не убирать. Ref указывает на корневой `<div>`, тот же элемент
102
+ попадает в `suggestRef` контекста.
103
+ - Корневой элемент — `<div>`. На него же вешается внутренний `onKeyDown`, поэтому событие
104
+ клавиатуры от любого потомка проходит через обработчик `Escape`.
105
+ - `size`, `onSelect`, `onFilter` и `onKeyDown` намеренно исключаются из `...restProps` —
106
+ иначе React отрендерит невалидные атрибуты на `<div>` и потеряет внутреннюю обработку.
107
+ - Закрытие по клику вне работает только если потребитель заполнил `dropdownRef`. Список
108
+ обычно живёт в портале и корневым `<div>` не покрывается.
109
+ - `Escape` останавливает всплытие **только когда список открыт** — на этом строится
110
+ вложенность suggest'а в модальные окна: закрытым suggest'ом `Escape` пробрасывается выше.
111
+ - Имена полей контекста — публичный API: `SuggestContext` и `useSuggestContext`
112
+ экспортируются из barrel и используются вне папки компонента (`ChipSuggest`).
113
+ - `SuggestMobileDropdownContent` из barrel **не экспортируется**, но импортируется
114
+ `ChipSuggest` по прямому пути. Переименование файла ломает `ChipSuggest`.
119
115
 
120
116
  ---
121
117
 
122
- ## Инварианты
118
+ ## Accessibility
123
119
 
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)`** — допустимо (сбрасывает выбор). Потребитель должен это учесть.
120
+ Собственных ARIA-атрибутов Suggest не выставляет — разметку управляющего элемента и
121
+ списка пишет потребитель. Компонент даёт для этого:
131
122
 
132
- ---
123
+ - `dropdownListId` — общий идентификатор для `id` списка и `aria-controls` на управляющем
124
+ элементе;
125
+ - `dropdownOpen` — значение для `aria-expanded`;
126
+ - `activeDescendant` / `setActiveDescendant` — для `aria-activedescendant` при клавиатурной
127
+ навигации по списку.
133
128
 
134
- ## Accessibility
129
+ Клавиатура: обработан только `Escape` — при открытом списке он закрывает список,
130
+ возвращает в поле ввода `label` текущего `value` и останавливает всплытие события.
131
+ Стрелки и `Enter` обрабатывает список потребителя (`DropdownList` / `DropdownListItem`
132
+ делают это сами). Возврат фокуса на управляющий элемент после закрытия Suggest не делает —
133
+ в `ChipSuggest` это решается `FocusTrap` вокруг содержимого дропдауна.
135
134
 
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.
135
+ Мобильное наполнение (`SuggestMobileDropdownContent`) рендерит список с `role="listbox"`
136
+ и опциями `role="option"` с `aria-selected`, поле ввода получает автофокус при открытии.
139
137
 
140
138
  ---
141
139
 
142
140
  ## Связанные компоненты
143
141
 
144
- - `SuggestField` — готовый UI поверх Suggest.
145
- - `Dropdown` — используется внутри SuggestField для выпадающего списка.
146
- - `SelectField` — для статического списка без фильтрации.
142
+ - `SuggestField` — готовое поле-suggest с лейблом, статусами, префиксом/постфиксом и
143
+ очисткой. Решает ту же задачу «из коробки»; на `Suggest` не построен и дублирует его
144
+ состояние своей реализацией.
145
+ - `Chip` — родитель `ChipSuggest`, единственного компонента библиотеки, собранного на
146
+ `Suggest`. `ChipSuggest` опирается не только на barrel, но и на внутренний
147
+ `SuggestMobileDropdownContent`.
148
+ - `Dropdown` — мобильное наполнение Suggest рендерится из его частей
149
+ (`DropdownMobileHeader`, `DropdownMobileInput`, `DropdownMobileList`, …); в десктопной
150
+ композиции `Dropdown` обычно и служит выпадающим списком.
151
+ - `SuggestMobileDropdownContent` — внутреннее наполнение полноэкранного мобильного
152
+ дропдауна: поле ввода, список опций, лоадер и текст при пустом списке. Данные берёт из
153
+ `SuggestContext`, поэтому рендерится только внутри `Suggest`. Отдельного AI.md не имеет,
154
+ из barrel не экспортируется.
147
155
 
148
156
  ---
149
157
 
150
158
  ## Stories
151
159
 
152
- Отдельных stories нет — демонстрируется через `SuggestField`.
160
+ Основные истории: `stories/Suggest/Suggest.stories.tsx`
161
+ Файлы примеров: `stories/Suggest/examples/`
162
+
163
+ | Story | Example file | Что демонстрирует |
164
+ |---|---|---|
165
+ | `Playground` | `Playground.tsx` | Интерактивный контроль `size`, `placeholder`, `noOptionsText`, `clearInputOnFocus`, `dropdownListLoading` |
166
+ | `Default` | `Default.tsx` | Минимальная композиция: Suggest + управляющий элемент на `TextField` + список на `Dropdown` |
167
+ | `Sizes` | `Sizes.tsx` | Размеры SM / MD / LG — размер раздаётся потребителем |
168
+ | `DropdownListLoading` | `DropdownListLoading.tsx` | Догрузка опций: лоадер под уже загруженным списком |
169
+ | `VisualTests` | `VisualTests.tsx` | Скриншот-регрессия: размеры, заполненное значение, открытый дропдаун. Пустой список в кадр не попадает — открыт может быть только один дропдаун |
170
+ | `VisualTestsDropdownListLoading` | `VisualTestsDropdownListLoading.tsx` | Скриншот-регрессия: открытый дропдаун с лоадером догрузки. Отдельная стори — два Suggest'а не могут быть открыты одновременно |
171
+
172
+ Скриншот-тестами покрыты только `VisualTests` и `VisualTestsDropdownListLoading`:
173
+ набор исключён из скриншотов на уровне `meta` (`testRunner: { skip: true }`), а эти две
174
+ стори включены обратно через `testRunner: { skip: false }` — как в `ModalWindow`.
175
+ Suggest headless: собственной разметки он не рендерит, поэтому в кадре закрытых стори
176
+ только обвязка примеров (`TextField`, `Dropdown`), у которых своё визуальное покрытие,
177
+ а baseline `Default` и `DropdownListLoading` совпадали побайтово. Ценность есть лишь
178
+ у кадров с открытым списком, которые ставит `play`-функция.
153
179
 
154
180
  ---
155
181
 
@@ -157,4 +183,414 @@ if (value !== prevValue) {
157
183
 
158
184
  | Дата | Изменение |
159
185
  |---|---|
160
- | 2026-04-18 | Создан документ |
186
+ | 2026-09-02 | Создан документ |
187
+
188
+ ---
189
+
190
+ ## Примеры использования
191
+
192
+ ### Default
193
+
194
+ `stories/Suggest/examples/Default.tsx`
195
+
196
+ ```tsx
197
+ import React, { useState } from "react";
198
+ import {
199
+ Dropdown,
200
+ DropdownList,
201
+ DropdownListItem,
202
+ EComponentSize,
203
+ EDropdownWidth,
204
+ Suggest,
205
+ TextField,
206
+ useSuggestContext,
207
+ type ISuggestOption,
208
+ } from "@sberbusiness/triplex-next";
209
+
210
+ const FRUITS: ISuggestOption[] = [
211
+ { id: "1", label: "Apple" },
212
+ { id: "2", label: "Banana" },
213
+ { id: "3", label: "Cherry" },
214
+ { id: "4", label: "Grapes" },
215
+ { id: "5", label: "Melon" },
216
+ ];
217
+
218
+ const SIZE = EComponentSize.LG;
219
+
220
+ /** Управляющий элемент. Значение поля ввода и состояние списка берутся из SuggestContext. */
221
+ const SuggestTarget = () => {
222
+ const { inputValue, dropdownOpen, dropdownListId, setDropdownOpen, onFilter } = useSuggestContext();
223
+
224
+ return (
225
+ <TextField
226
+ label="Fruit"
227
+ size={SIZE}
228
+ inputProps={{
229
+ value: inputValue,
230
+ placeholder: "Type to proceed",
231
+ role: "combobox",
232
+ "aria-expanded": dropdownOpen,
233
+ "aria-controls": dropdownListId,
234
+ onChange: (event) => {
235
+ onFilter(event.target.value);
236
+ setDropdownOpen(true);
237
+ },
238
+ onClick: () => setDropdownOpen(true),
239
+ }}
240
+ />
241
+ );
242
+ };
243
+
244
+ /** Выпадающий список. dropdownRef обязателен: по нему Suggest отличает клик внутри списка от клика вне компонента. */
245
+ const SuggestDropdown = () => {
246
+ const {
247
+ value,
248
+ options,
249
+ noOptionsText,
250
+ dropdownOpen,
251
+ dropdownListId,
252
+ suggestRef,
253
+ dropdownRef,
254
+ setDropdownOpen,
255
+ closeDropdown,
256
+ onSelect,
257
+ } = useSuggestContext();
258
+
259
+ return (
260
+ <Dropdown
261
+ ref={dropdownRef}
262
+ size={SIZE}
263
+ targetRef={suggestRef}
264
+ width={EDropdownWidth.TARGET}
265
+ opened={dropdownOpen}
266
+ setOpened={(opened) => (opened ? setDropdownOpen(true) : closeDropdown())}
267
+ >
268
+ {options.length === 0 ? (
269
+ // id остаётся на контейнере и в пустом состоянии — иначе aria-controls
270
+ // управляющего элемента ссылается на несуществующий элемент.
271
+ <div id={dropdownListId} style={{ padding: "12px 16px" }}>
272
+ {noOptionsText}
273
+ </div>
274
+ ) : (
275
+ <DropdownList id={dropdownListId} size={SIZE} dropdownOpened={dropdownOpen}>
276
+ {options.map((option) => (
277
+ <DropdownListItem
278
+ key={option.id}
279
+ id={option.id}
280
+ selected={option.id === value?.id}
281
+ onSelect={() => onSelect(option)}
282
+ >
283
+ {option.label}
284
+ </DropdownListItem>
285
+ ))}
286
+ </DropdownList>
287
+ )}
288
+ </Dropdown>
289
+ );
290
+ };
291
+
292
+ export const Default = () => {
293
+ const [value, setValue] = useState<ISuggestOption>();
294
+ const [options, setOptions] = useState<ISuggestOption[]>(FRUITS);
295
+
296
+ const handleFilter = (inputValue: string) =>
297
+ setOptions(FRUITS.filter(({ label }) => label.toLowerCase().includes(inputValue.toLowerCase())));
298
+
299
+ return (
300
+ <div style={{ maxWidth: "300px" }}>
301
+ <Suggest
302
+ value={value}
303
+ options={options}
304
+ size={SIZE}
305
+ noOptionsText="No matches found."
306
+ onSelect={setValue}
307
+ onFilter={handleFilter}
308
+ >
309
+ <SuggestTarget />
310
+ <SuggestDropdown />
311
+ </Suggest>
312
+ </div>
313
+ );
314
+ };
315
+ ```
316
+
317
+ ### Sizes
318
+
319
+ `stories/Suggest/examples/Sizes.tsx`
320
+
321
+ ```tsx
322
+ import React, { useState } from "react";
323
+ import {
324
+ Dropdown,
325
+ DropdownList,
326
+ DropdownListItem,
327
+ EComponentSize,
328
+ EDropdownWidth,
329
+ Suggest,
330
+ TextField,
331
+ useSuggestContext,
332
+ type ISuggestOption,
333
+ } from "@sberbusiness/triplex-next";
334
+
335
+ const FRUITS: ISuggestOption[] = [
336
+ { id: "1", label: "Apple" },
337
+ { id: "2", label: "Banana" },
338
+ { id: "3", label: "Cherry" },
339
+ { id: "4", label: "Grapes" },
340
+ { id: "5", label: "Melon" },
341
+ ];
342
+
343
+ const SIZES = Object.values(EComponentSize);
344
+
345
+ interface ISuggestPartProps {
346
+ size: EComponentSize;
347
+ }
348
+
349
+ /** Управляющий элемент. Suggest сам размер не отображает — прокидывайте его в поле ввода и список. */
350
+ const SuggestTarget = ({ size }: ISuggestPartProps) => {
351
+ const { inputValue, dropdownOpen, dropdownListId, setDropdownOpen, onFilter } = useSuggestContext();
352
+
353
+ return (
354
+ <TextField
355
+ label="Fruit"
356
+ size={size}
357
+ inputProps={{
358
+ value: inputValue,
359
+ placeholder: "Type to proceed",
360
+ role: "combobox",
361
+ "aria-expanded": dropdownOpen,
362
+ "aria-controls": dropdownListId,
363
+ onChange: (event) => {
364
+ onFilter(event.target.value);
365
+ setDropdownOpen(true);
366
+ },
367
+ onClick: () => setDropdownOpen(true),
368
+ }}
369
+ />
370
+ );
371
+ };
372
+
373
+ /** Выпадающий список. dropdownRef обязателен: по нему Suggest отличает клик внутри списка от клика вне компонента. */
374
+ const SuggestDropdown = ({ size }: ISuggestPartProps) => {
375
+ const {
376
+ value,
377
+ options,
378
+ noOptionsText,
379
+ dropdownOpen,
380
+ dropdownListId,
381
+ suggestRef,
382
+ dropdownRef,
383
+ setDropdownOpen,
384
+ closeDropdown,
385
+ onSelect,
386
+ } = useSuggestContext();
387
+
388
+ return (
389
+ <Dropdown
390
+ ref={dropdownRef}
391
+ size={size}
392
+ targetRef={suggestRef}
393
+ width={EDropdownWidth.TARGET}
394
+ opened={dropdownOpen}
395
+ setOpened={(opened) => (opened ? setDropdownOpen(true) : closeDropdown())}
396
+ >
397
+ {options.length === 0 ? (
398
+ // id остаётся на контейнере и в пустом состоянии — иначе aria-controls
399
+ // управляющего элемента ссылается на несуществующий элемент.
400
+ <div id={dropdownListId} style={{ padding: "12px 16px" }}>
401
+ {noOptionsText}
402
+ </div>
403
+ ) : (
404
+ <DropdownList id={dropdownListId} size={size} dropdownOpened={dropdownOpen}>
405
+ {options.map((option) => (
406
+ <DropdownListItem
407
+ key={option.id}
408
+ id={option.id}
409
+ selected={option.id === value?.id}
410
+ onSelect={() => onSelect(option)}
411
+ >
412
+ {option.label}
413
+ </DropdownListItem>
414
+ ))}
415
+ </DropdownList>
416
+ )}
417
+ </Dropdown>
418
+ );
419
+ };
420
+
421
+ const SizeItem = ({ size }: ISuggestPartProps) => {
422
+ const [value, setValue] = useState<ISuggestOption>();
423
+ const [options, setOptions] = useState<ISuggestOption[]>(FRUITS);
424
+
425
+ const handleFilter = (inputValue: string) =>
426
+ setOptions(FRUITS.filter(({ label }) => label.toLowerCase().includes(inputValue.toLowerCase())));
427
+
428
+ return (
429
+ <div>
430
+ <div style={{ marginBottom: "8px", fontSize: "16px", fontWeight: "700" }}>{size.toUpperCase()}</div>
431
+ <Suggest
432
+ value={value}
433
+ options={options}
434
+ size={size}
435
+ noOptionsText="No matches found."
436
+ onSelect={setValue}
437
+ onFilter={handleFilter}
438
+ >
439
+ <SuggestTarget size={size} />
440
+ <SuggestDropdown size={size} />
441
+ </Suggest>
442
+ </div>
443
+ );
444
+ };
445
+
446
+ export const Sizes = () => (
447
+ <div style={{ maxWidth: "300px", display: "flex", flexDirection: "column", gap: "16px" }}>
448
+ {SIZES.map((size) => (
449
+ <SizeItem key={size} size={size} />
450
+ ))}
451
+ </div>
452
+ );
453
+ ```
454
+
455
+ ### DropdownListLoading
456
+
457
+ `stories/Suggest/examples/DropdownListLoading.tsx`
458
+
459
+ ```tsx
460
+ import React, { useRef, useState } from "react";
461
+ import {
462
+ Dropdown,
463
+ DropdownList,
464
+ DropdownListItem,
465
+ EComponentSize,
466
+ EDropdownWidth,
467
+ Suggest,
468
+ TextField,
469
+ useSuggestContext,
470
+ type ISuggestOption,
471
+ } from "@sberbusiness/triplex-next";
472
+
473
+ const FRUITS: ISuggestOption[] = [
474
+ { id: "1", label: "Apple" },
475
+ { id: "2", label: "Banana" },
476
+ { id: "3", label: "Cherry" },
477
+ { id: "4", label: "Grapes" },
478
+ { id: "5", label: "Melon" },
479
+ ];
480
+
481
+ const SIZE = EComponentSize.LG;
482
+ const REQUEST_DELAY = 700;
483
+
484
+ /** Управляющий элемент. Значение поля ввода и состояние списка берутся из SuggestContext. */
485
+ const SuggestTarget = () => {
486
+ const { inputValue, dropdownOpen, dropdownListId, setDropdownOpen, onFilter } = useSuggestContext();
487
+
488
+ return (
489
+ <TextField
490
+ label="Fruit"
491
+ size={SIZE}
492
+ inputProps={{
493
+ value: inputValue,
494
+ placeholder: "Type to proceed",
495
+ role: "combobox",
496
+ "aria-expanded": dropdownOpen,
497
+ "aria-controls": dropdownListId,
498
+ onChange: (event) => {
499
+ onFilter(event.target.value);
500
+ setDropdownOpen(true);
501
+ },
502
+ onClick: () => setDropdownOpen(true),
503
+ }}
504
+ />
505
+ );
506
+ };
507
+
508
+ /** Выпадающий список. dropdownListLoading дорисовывает лоадер под уже загруженными опциями. */
509
+ const SuggestDropdown = () => {
510
+ const {
511
+ value,
512
+ options,
513
+ noOptionsText,
514
+ dropdownOpen,
515
+ dropdownListId,
516
+ dropdownListLoading,
517
+ suggestRef,
518
+ dropdownRef,
519
+ setDropdownOpen,
520
+ closeDropdown,
521
+ onSelect,
522
+ } = useSuggestContext();
523
+
524
+ return (
525
+ <Dropdown
526
+ ref={dropdownRef}
527
+ size={SIZE}
528
+ targetRef={suggestRef}
529
+ width={EDropdownWidth.TARGET}
530
+ opened={dropdownOpen}
531
+ setOpened={(opened) => (opened ? setDropdownOpen(true) : closeDropdown())}
532
+ >
533
+ {options.length === 0 && !dropdownListLoading ? (
534
+ // id остаётся на контейнере и в пустом состоянии — иначе aria-controls
535
+ // управляющего элемента ссылается на несуществующий элемент.
536
+ <div id={dropdownListId} style={{ padding: "12px 16px" }}>
537
+ {noOptionsText}
538
+ </div>
539
+ ) : (
540
+ <DropdownList
541
+ id={dropdownListId}
542
+ size={SIZE}
543
+ dropdownOpened={dropdownOpen}
544
+ loading={dropdownListLoading}
545
+ >
546
+ {options.map((option) => (
547
+ <DropdownListItem
548
+ key={option.id}
549
+ id={option.id}
550
+ selected={option.id === value?.id}
551
+ onSelect={() => onSelect(option)}
552
+ >
553
+ {option.label}
554
+ </DropdownListItem>
555
+ ))}
556
+ </DropdownList>
557
+ )}
558
+ </Dropdown>
559
+ );
560
+ };
561
+
562
+ export const DropdownListLoading = () => {
563
+ const [value, setValue] = useState<ISuggestOption>();
564
+ const [options, setOptions] = useState<ISuggestOption[]>(FRUITS);
565
+ const [loading, setLoading] = useState(false);
566
+ const requestRef = useRef<ReturnType<typeof setTimeout>>();
567
+
568
+ const handleFilter = (inputValue: string) => {
569
+ clearTimeout(requestRef.current);
570
+ setLoading(true);
571
+
572
+ requestRef.current = setTimeout(() => {
573
+ setOptions(FRUITS.filter(({ label }) => label.toLowerCase().includes(inputValue.toLowerCase())));
574
+ setLoading(false);
575
+ }, REQUEST_DELAY);
576
+ };
577
+
578
+ return (
579
+ <div style={{ maxWidth: "300px" }}>
580
+ <Suggest
581
+ value={value}
582
+ options={options}
583
+ size={SIZE}
584
+ noOptionsText="No matches found."
585
+ dropdownListLoading={loading}
586
+ onSelect={setValue}
587
+ onFilter={handleFilter}
588
+ >
589
+ <SuggestTarget />
590
+ <SuggestDropdown />
591
+ </Suggest>
592
+ </div>
593
+ );
594
+ };
595
+ ```
596
+