@sberbusiness/triplex-next-mcp-server 1.50.0-beta → 1.53.1
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 +1 -0
- package/bundle/components/AlertProcess.md +6 -3
- package/bundle/components/AlertProcessSpoiler.md +216 -0
- package/bundle/components/Badge.md +4 -3
- package/bundle/components/ButtonDropdown.md +5 -3
- package/bundle/components/Carousel.md +671 -0
- package/bundle/components/CarouselExtended.md +7 -5
- package/bundle/components/CarouselIndicators.md +666 -0
- package/bundle/components/CarouselItem.md +579 -0
- package/bundle/components/CarouselNextButton.md +633 -0
- package/bundle/components/CarouselPrevButton.md +633 -0
- package/bundle/components/CarouselViewport.md +631 -0
- package/bundle/components/CheckboxTreeExtended.md +21 -7
- package/bundle/components/CheckboxTreeExtendedCheckbox.md +591 -0
- package/bundle/components/CheckboxTreeExtendedNode.md +577 -0
- package/bundle/components/Chip.md +1 -0
- package/bundle/components/ChipDatePicker.md +332 -0
- package/bundle/components/ChipDatePickerTarget.md +316 -0
- package/bundle/components/ChipMonthYearPicker.md +4 -3
- package/bundle/components/ChipMonthYearPickerTarget.md +318 -0
- package/bundle/components/ChipMultiselect.md +63 -73
- package/bundle/components/ChipSelect.md +385 -0
- package/bundle/components/ChipSelectTarget.md +388 -0
- package/bundle/components/ChipSuggest.md +10 -16
- package/bundle/components/ChipSuggestTarget.md +506 -0
- package/bundle/components/CollapsibleTree.md +2 -1
- package/bundle/components/CollapsibleTreeExtended.md +2 -1
- package/bundle/components/DateField.md +4 -2
- package/bundle/components/DatePickerExtended.md +16 -14
- package/bundle/components/DateRange.md +17 -10
- package/bundle/components/Dropdown.md +36 -63
- package/bundle/components/EmptyView.md +9 -4
- package/bundle/components/ExpandAnimation.md +9 -5
- package/bundle/components/FormField.md +1 -1
- package/bundle/components/ImageGallery.md +19 -52
- package/bundle/components/ImageGalleryExtended.md +123 -139
- package/bundle/components/ListItemControlsButtonDropdown.md +3 -2
- package/bundle/components/LoaderSmall.md +5 -4
- package/bundle/components/MonthYearRange.md +334 -0
- package/bundle/components/MultiselectField.md +81 -143
- package/bundle/components/Notification.md +2 -1
- package/bundle/components/NotificationGrouped.md +174 -0
- package/bundle/components/NumberField.md +259 -116
- package/bundle/components/OrderedList.md +149 -112
- package/bundle/components/Overlay.md +3 -2
- package/bundle/components/PageIndicators.md +306 -0
- package/bundle/components/Pagination.md +81 -3
- package/bundle/components/PaginationExtended.md +160 -0
- package/bundle/components/Portal.md +5 -2
- package/bundle/components/Puller.md +375 -0
- package/bundle/components/SMSField.md +277 -102
- package/bundle/components/SelectExtendedField.md +11 -7
- package/bundle/components/SelectField.md +7 -4
- package/bundle/components/SmartSpot.md +259 -0
- package/bundle/components/Step.md +2 -1
- package/bundle/components/Stepper.md +7 -7
- package/bundle/components/Suggest.md +7 -6
- package/bundle/components/SuggestField.md +8 -6
- package/bundle/components/TableBasic.md +12 -14
- package/bundle/components/Tooltip.md +9 -8
- package/bundle/guides/commits.md +4 -1
- package/bundle/guides/context.md +5 -5
- package/bundle/guides/stories.md +29 -21
- package/bundle/manifest.json +145 -13
- package/bundle/release-notes/1.50.0.mdx +4 -0
- package/bundle/release-notes/1.51.0.mdx +57 -0
- package/bundle/release-notes/1.52.0.mdx +55 -0
- package/bundle/release-notes/1.53.0.mdx +60 -0
- package/bundle/release-notes/1.54.0.mdx +45 -0
- package/bundle/release-notes/1.55.0.mdx +7 -0
- package/bundle/tokens.json +377 -222
- package/dist/index.js +39 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
---
|
|
2
|
+
component: Puller
|
|
3
|
+
category: Puller
|
|
4
|
+
related: [Overlay, Portal, ButtonIcon, LoaderSmall, Badge, FormField]
|
|
5
|
+
tokens:
|
|
6
|
+
- Puller.Backdrop
|
|
7
|
+
- Puller.Body_Background
|
|
8
|
+
- Puller.Border_Color
|
|
9
|
+
- Puller.Footer_Background
|
|
10
|
+
- Puller.Header_Background
|
|
11
|
+
- PullerList.Active_Background
|
|
12
|
+
- PullerList.Selected_Background
|
|
13
|
+
- FormField.Input_Color_Default
|
|
14
|
+
- FormField.Placeholder_Color
|
|
15
|
+
stories: stories/Puller/Puller.stories.tsx
|
|
16
|
+
version: "1.0"
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
# Puller
|
|
20
|
+
|
|
21
|
+
## Назначение
|
|
22
|
+
|
|
23
|
+
Нижняя шторка (bottom sheet) на базе `OverlayBase` для экранов любой ширины: выезжает снизу на всю ширину поверх подложки.
|
|
24
|
+
|
|
25
|
+
Используй когда: нужна шторка, которая открывается не от выпадающего меню.
|
|
26
|
+
Не используй когда:
|
|
27
|
+
|
|
28
|
+
- нужен выпадающий блок, который на десктопе — меню, а на мобильном — шторка. Используй `Dropdown` с `pullerProps`: он сам переключает версии через `MobileView`;
|
|
29
|
+
- нужна подсказка — используй `Tooltip`, его мобильная версия уже построена на `Puller`;
|
|
30
|
+
- нужно модальное окно или боковая панель — используй `ModalWindow` или `Overlay`.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Варианты и props
|
|
35
|
+
|
|
36
|
+
### `Puller`
|
|
37
|
+
|
|
38
|
+
`IPullerProps` расширяет `React.HTMLAttributes<HTMLDivElement>`. Атрибуты распределяются так: `className` попадает на фиксированный wrapper (`.pullerWrapper`), всё остальное (`id`, `role`, `aria-*`, `style`, обработчики) — на контентный `<div>` (`.puller`), туда же ведёт `ref`.
|
|
39
|
+
|
|
40
|
+
#### Обязательные props
|
|
41
|
+
|
|
42
|
+
| Prop | Тип | Описание |
|
|
43
|
+
|---|---|---|
|
|
44
|
+
| `opened` | `boolean` | Состояние открытости: компонент полностью управляемый, собственного state открытости нет |
|
|
45
|
+
| `setOpened` | `(opened: boolean) => void` | Вызывается с `false` по `touchstart` / `mousedown` на подложке (только когда `opened === true`) |
|
|
46
|
+
|
|
47
|
+
#### Опциональные props
|
|
48
|
+
|
|
49
|
+
| Prop | Тип | По умолчанию | Описание |
|
|
50
|
+
|---|---|---|---|
|
|
51
|
+
| `renderContainer` | `Element \| DocumentFragment` | `document.body` | DOM-узел, в который рендерится шторка |
|
|
52
|
+
|
|
53
|
+
Остальные — HTML-атрибуты `<div>`.
|
|
54
|
+
|
|
55
|
+
### Субкомпоненты
|
|
56
|
+
|
|
57
|
+
| Компонент | Собственные props | Описание |
|
|
58
|
+
|---|---|---|
|
|
59
|
+
| `PullerHeader` | `controlButtons?: ReactNode` | Шапка. `children` — слева (сжимается, `min-width: 0`), `controlButtons` — справа: обычно `PullerLoader` и `PullerClose` |
|
|
60
|
+
| `PullerBody` | — | Скроллируемое тело (`overflow: auto`, `flex-grow: 1`) |
|
|
61
|
+
| `PullerFooter` | — | Футер для кнопок, с верхней границей |
|
|
62
|
+
| `PullerClose` | `IButtonIconProps` без `children` | Кнопка закрытия: `ButtonIcon` с иконкой-крестиком. Сама шторку не закрывает: `onClick` и `aria-label` передаёт потребитель |
|
|
63
|
+
| `PullerList` | `loading?: boolean` | Контейнер списка с `role="listbox"`. При `loading` в конец добавляется `LoaderSmall` MD, скрытый от скринридеров (`aria-hidden`): загрузку передаёт `aria-busy` на `listbox` |
|
|
64
|
+
| `PullerListItem` | `id: string`, `onSelect?`, `selected?`, `showNotificationIcon?` | Элемент списка с `role="option"`. `onSelect` вызывается по клику до `onClick`. Строковый `children` дублируется в `title` |
|
|
65
|
+
| `PullerLoader` | `ILoaderSmallProps` без `theme` / `size` | Лоадер для шапки: `LoaderSmall`, тема `BRAND`, размер SM |
|
|
66
|
+
| `PullerInput` | `InputHTMLAttributes` | Нативный `<input>` для шапки, без рамки и фона |
|
|
67
|
+
| `PullerMaskedInput` | `IFormFieldMaskedInputProps` | Маскированное поле для шапки: обёртка над `FormFieldMaskedInput` со статическим `presets` (тот же объект, что у `FormFieldMaskedInput`) |
|
|
68
|
+
|
|
69
|
+
### Ограничения
|
|
70
|
+
|
|
71
|
+
- `.pullerWrapper` — `position: fixed`: `transform`, `filter`, `perspective`, `contain` или собственный stacking context у `renderContainer` и его родителей сдвинут, обрежут или перекроют шторку.
|
|
72
|
+
- Пока шторка открыта, скролл блокируется на `body`, а не на `renderContainer`.
|
|
73
|
+
- Шторка не закрывается по `Escape` и не перехватывает фокус — это добавляет потребитель (как `MultiselectField.Dropdown` и `DatePickerExtended`).
|
|
74
|
+
- Высота ограничена `50vh`: длинное содержимое кладётся в прокручиваемый `PullerBody`.
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Дизайн-токены
|
|
79
|
+
|
|
80
|
+
Переопределяются через `ThemeProvider` (prop `tokens`) — см. `ThemeProvider-ai.md` →
|
|
81
|
+
«Как переопределять токены». Значения по умолчанию лежат в файлах групп `Puller` и `PullerList` в `src/components/DesignTokens/components/`.
|
|
82
|
+
|
|
83
|
+
```text
|
|
84
|
+
Puller.Backdrop
|
|
85
|
+
Puller.Body_Background
|
|
86
|
+
Puller.Border_Color
|
|
87
|
+
Puller.Footer_Background
|
|
88
|
+
Puller.Header_Background
|
|
89
|
+
PullerList.Active_Background
|
|
90
|
+
PullerList.Selected_Background
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
`PullerInput` дополнительно использует токены поля ввода: `FormField.Input_Color_Default`, `FormField.Placeholder_Color`.
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## Инварианты
|
|
98
|
+
|
|
99
|
+
- `forwardRef` на `Puller` и всех субкомпонентах, кроме `PullerLoader`, — не убирать. Ref `Puller` указывает на контентный `<div>` (`.puller`), а не на wrapper. На это опирается `Dropdown`: его ref в мобильном режиме ведёт сюда.
|
|
100
|
+
- `Puller` рендерится через `Portal`, по умолчанию в `document.body`. Смена контейнера по умолчанию — breaking change для потребителей, которые ищут шторку в DOM.
|
|
101
|
+
- В закрытом состоянии (`opened`, `opening` и `closing` равны `false`) `PullerInner` возвращает `null`. Анимация закрытия доигрывает до `transitionend` на подложке и только потом снимает разметку.
|
|
102
|
+
- Непрозрачность подложки задаётся альфа-каналом токена `Puller.Backdrop`; `opacity` подложки только анимирует появление (0 → 1).
|
|
103
|
+
- Анимация открытия требует сначала отрендерить шторку закрытой: `openingState` / `openedState` выставляются через `setTimeout`. Не заменять на синхронную установку, иначе переход не проиграется.
|
|
104
|
+
- Пока `opened === true`, на `document.body` висит класс `pullerScrollLock` (`overflow: hidden`). Класс вешается только через `lockBodyScroll` (`src/utils/scroll/bodyScrollLock.ts`): прямой `classList.remove` снял бы блокировку при другой открытой шторке.
|
|
105
|
+
- Компонент управляемый: не добавлять внутренний state открытости.
|
|
106
|
+
- На корневые классы `.puller`, `.pullerWrapper`, `.pullerBackdrop` опираются тесты `MultiselectFieldDropdown.test.tsx` и `SuggestFieldMobile.test.tsx`. Переименование — breaking change.
|
|
107
|
+
- `IPullerHeaderProps` объявлен в `PullerHeader.tsx`, его расширяет `ITooltipMobileHeaderProps` (`src/components/Tooltip/types.ts`). Перенос типа ломает deep-импорты.
|
|
108
|
+
- Публичный barrel `src/components/Puller/index.ts` (включая `PullerMaskedInput`) должен сохраняться. `PullerInner` — внутренняя разметка, в barrel не входит.
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## Accessibility
|
|
113
|
+
|
|
114
|
+
- Роль диалога шторка сама не ставит: `role="dialog"` и `aria-label` / `aria-labelledby` потребитель передаёт на `Puller` (они попадают на контентный `<div>`).
|
|
115
|
+
- `PullerList` — `role="listbox"`, `PullerListItem` — `role="option"` с `aria-selected`. Клавиатурной навигации по списку нет.
|
|
116
|
+
- `PullerClose` — кнопка-иконка без текста: `aria-label` передаёт потребитель. `ButtonDropdown`, `SelectExtendedFieldDropdownDefault`, `DatePickerExtendedDropdown`, `ListItemControlsButtonDropdown`, `SuggestMobileDropdownContent` и `SuggestFieldMobileDropdown` его пока не передают.
|
|
117
|
+
- `PullerInput` — нативный `<input>` без видимой подписи: `aria-label` передаёт потребитель.
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## Связанные компоненты
|
|
122
|
+
|
|
123
|
+
- `Overlay` — семейство, к которому относится `OverlayBase`: `Puller` рендерит `OverlayBase` с `direction={EOverlayDirection.BOTTOM}` и получает от него флаги `opening` / `closing` и сеттеры анимации.
|
|
124
|
+
- `Portal` — выносит шторку из дерева родителя, см. «Инварианты».
|
|
125
|
+
- `ButtonIcon` — база `PullerClose`, `IPullerCloseProps` расширяет `IButtonIconProps`.
|
|
126
|
+
- `LoaderSmall` — рендерится в `PullerLoader` (тема `BRAND`, SM) и в элементе-лоадере `PullerList` (MD). `IPullerLoaderProps` расширяет `ILoaderSmallProps`.
|
|
127
|
+
- `Badge` — `Badge.Dot` в углу `PullerListItem` при `showNotificationIcon`.
|
|
128
|
+
- `FormField` — `PullerMaskedInput` оборачивает `FormFieldMaskedInput` и наследует `IFormFieldMaskedInputProps`. Токены поля ввода использует `PullerInput`.
|
|
129
|
+
- `Text` — `PullerListItem` рендерится как `Text` размера `B3`.
|
|
130
|
+
- `Dropdown`, `Tooltip` — в адаптивном режиме рендерят `Puller` вместо десктопной версии.
|
|
131
|
+
- `MobileView` — переключает версии в `Dropdown` и `Tooltip`; сам `Puller` ширину экрана не проверяет.
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## Stories
|
|
136
|
+
|
|
137
|
+
Основные истории: `stories/Puller/Puller.stories.tsx`
|
|
138
|
+
Файлы примеров: `stories/Puller/examples/`
|
|
139
|
+
|
|
140
|
+
Примеры стартуют закрытыми. Шторку открывает `play`; на docs-странице (`viewMode === "docs"`) он ничего не делает.
|
|
141
|
+
|
|
142
|
+
| Story | Example file | Что демонстрирует |
|
|
143
|
+
|---|---|---|
|
|
144
|
+
| `Playground` | `Playground.tsx` | Интерактивный контроль: начальное `opened`, заголовок, `listLoading` (`loading` списка), наличие футера |
|
|
145
|
+
| `Default` | `Default.tsx` | Кнопка открывает шторку: шапка с заголовком и `PullerClose`, тело, футер с кнопкой |
|
|
146
|
+
| `WithList` | `WithList.tsx` | `PullerList` с выбранным элементом, значком уведомления и `loading`; выбор закрывает шторку |
|
|
147
|
+
| `WithInput` | `WithInput.tsx` | Шапка с `PullerInput`, `PullerLoader` и `PullerClose` — раскладка мобильного suggest |
|
|
148
|
+
| `RenderContainer` («Render container») | `RenderContainer.tsx` | Рендер шторки в заданный контейнер через `renderContainer` |
|
|
149
|
+
| `VisualTests` | `VisualTests.tsx` | Длинный заголовок, лоадер, скролл длинного списка, выбранный элемент, значок, футер с двумя кнопками |
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## История изменений
|
|
154
|
+
|
|
155
|
+
| Дата | Изменение |
|
|
156
|
+
|---|---|
|
|
157
|
+
| 2026-10-21 | Создан документ (TRIPLEX-961). |
|
|
158
|
+
| 2026-10-31 | **Breaking**: `Puller` сам рендерится через `Portal`: по умолчанию в `document.body`, контейнер задаёт новый prop `renderContainer` (TRIPLEX-961). |
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## Примеры использования
|
|
163
|
+
|
|
164
|
+
### Default
|
|
165
|
+
|
|
166
|
+
`stories/Puller/examples/Default.tsx`
|
|
167
|
+
|
|
168
|
+
```tsx
|
|
169
|
+
import React, { useState } from "react";
|
|
170
|
+
import {
|
|
171
|
+
Button,
|
|
172
|
+
EButtonTheme,
|
|
173
|
+
EComponentSize,
|
|
174
|
+
ETextSize,
|
|
175
|
+
Puller,
|
|
176
|
+
PullerBody,
|
|
177
|
+
PullerClose,
|
|
178
|
+
PullerFooter,
|
|
179
|
+
PullerHeader,
|
|
180
|
+
Text,
|
|
181
|
+
} from "@sberbusiness/triplex-next";
|
|
182
|
+
|
|
183
|
+
export const Default = () => {
|
|
184
|
+
const [opened, setOpened] = useState(false);
|
|
185
|
+
|
|
186
|
+
return (
|
|
187
|
+
<div>
|
|
188
|
+
<Button
|
|
189
|
+
theme={EButtonTheme.SECONDARY}
|
|
190
|
+
size={EComponentSize.MD}
|
|
191
|
+
aria-haspopup="dialog"
|
|
192
|
+
aria-expanded={opened}
|
|
193
|
+
onClick={() => setOpened(true)}
|
|
194
|
+
>
|
|
195
|
+
Open
|
|
196
|
+
</Button>
|
|
197
|
+
<Puller opened={opened} setOpened={setOpened} role="dialog" aria-labelledby="puller-default-title">
|
|
198
|
+
<PullerHeader controlButtons={<PullerClose aria-label="Close" onClick={() => setOpened(false)} />}>
|
|
199
|
+
<Text id="puller-default-title" size={ETextSize.B1}>
|
|
200
|
+
Title
|
|
201
|
+
</Text>
|
|
202
|
+
</PullerHeader>
|
|
203
|
+
<PullerBody>
|
|
204
|
+
<Text tag="div" size={ETextSize.B3} style={{ padding: "16px" }}>
|
|
205
|
+
Puller content. The puller closes via the close button, the footer button or a tap on the
|
|
206
|
+
backdrop.
|
|
207
|
+
</Text>
|
|
208
|
+
</PullerBody>
|
|
209
|
+
<PullerFooter>
|
|
210
|
+
<Button
|
|
211
|
+
theme={EButtonTheme.GENERAL}
|
|
212
|
+
size={EComponentSize.MD}
|
|
213
|
+
block
|
|
214
|
+
onClick={() => setOpened(false)}
|
|
215
|
+
>
|
|
216
|
+
Done
|
|
217
|
+
</Button>
|
|
218
|
+
</PullerFooter>
|
|
219
|
+
</Puller>
|
|
220
|
+
</div>
|
|
221
|
+
);
|
|
222
|
+
};
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### WithList
|
|
226
|
+
|
|
227
|
+
`stories/Puller/examples/WithList.tsx`
|
|
228
|
+
|
|
229
|
+
```tsx
|
|
230
|
+
import React, { useState } from "react";
|
|
231
|
+
import {
|
|
232
|
+
Button,
|
|
233
|
+
EButtonTheme,
|
|
234
|
+
EComponentSize,
|
|
235
|
+
ETextSize,
|
|
236
|
+
Puller,
|
|
237
|
+
PullerBody,
|
|
238
|
+
PullerClose,
|
|
239
|
+
PullerHeader,
|
|
240
|
+
PullerList,
|
|
241
|
+
PullerListItem,
|
|
242
|
+
Text,
|
|
243
|
+
} from "@sberbusiness/triplex-next";
|
|
244
|
+
|
|
245
|
+
const OPTIONS = [
|
|
246
|
+
{ id: "with-list-option-1", label: "Value 1" },
|
|
247
|
+
{ id: "with-list-option-2", label: "Value 2" },
|
|
248
|
+
{ id: "with-list-option-3", label: "Value 3", hasNotifications: true },
|
|
249
|
+
];
|
|
250
|
+
|
|
251
|
+
export const WithList = () => {
|
|
252
|
+
const [opened, setOpened] = useState(false);
|
|
253
|
+
const [selectedId, setSelectedId] = useState("with-list-option-2");
|
|
254
|
+
|
|
255
|
+
const handleSelect = (id: string) => {
|
|
256
|
+
setSelectedId(id);
|
|
257
|
+
setOpened(false);
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
return (
|
|
261
|
+
<div>
|
|
262
|
+
<Button
|
|
263
|
+
theme={EButtonTheme.SECONDARY}
|
|
264
|
+
size={EComponentSize.MD}
|
|
265
|
+
aria-haspopup="dialog"
|
|
266
|
+
aria-expanded={opened}
|
|
267
|
+
onClick={() => setOpened(true)}
|
|
268
|
+
>
|
|
269
|
+
Open
|
|
270
|
+
</Button>
|
|
271
|
+
<Puller opened={opened} setOpened={setOpened} role="dialog" aria-labelledby="puller-with-list-title">
|
|
272
|
+
<PullerHeader controlButtons={<PullerClose aria-label="Close" onClick={() => setOpened(false)} />}>
|
|
273
|
+
<Text id="puller-with-list-title" size={ETextSize.B1}>
|
|
274
|
+
Select a value
|
|
275
|
+
</Text>
|
|
276
|
+
</PullerHeader>
|
|
277
|
+
<PullerBody>
|
|
278
|
+
<PullerList loading aria-labelledby="puller-with-list-title">
|
|
279
|
+
{OPTIONS.map((option) => (
|
|
280
|
+
<PullerListItem
|
|
281
|
+
key={option.id}
|
|
282
|
+
id={option.id}
|
|
283
|
+
selected={option.id === selectedId}
|
|
284
|
+
showNotificationIcon={option.hasNotifications}
|
|
285
|
+
onSelect={() => handleSelect(option.id)}
|
|
286
|
+
>
|
|
287
|
+
{option.label}
|
|
288
|
+
</PullerListItem>
|
|
289
|
+
))}
|
|
290
|
+
</PullerList>
|
|
291
|
+
</PullerBody>
|
|
292
|
+
</Puller>
|
|
293
|
+
</div>
|
|
294
|
+
);
|
|
295
|
+
};
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### WithInput
|
|
299
|
+
|
|
300
|
+
`stories/Puller/examples/WithInput.tsx`
|
|
301
|
+
|
|
302
|
+
```tsx
|
|
303
|
+
import React, { useState } from "react";
|
|
304
|
+
import {
|
|
305
|
+
Button,
|
|
306
|
+
EButtonTheme,
|
|
307
|
+
EComponentSize,
|
|
308
|
+
Puller,
|
|
309
|
+
PullerBody,
|
|
310
|
+
PullerClose,
|
|
311
|
+
PullerHeader,
|
|
312
|
+
PullerInput,
|
|
313
|
+
PullerList,
|
|
314
|
+
PullerListItem,
|
|
315
|
+
PullerLoader,
|
|
316
|
+
} from "@sberbusiness/triplex-next";
|
|
317
|
+
|
|
318
|
+
const OPTIONS = [
|
|
319
|
+
{ id: "with-input-option-1", label: "Moscow" },
|
|
320
|
+
{ id: "with-input-option-2", label: "Mombasa" },
|
|
321
|
+
{ id: "with-input-option-3", label: "Montreal" },
|
|
322
|
+
];
|
|
323
|
+
|
|
324
|
+
export const WithInput = () => {
|
|
325
|
+
const [opened, setOpened] = useState(false);
|
|
326
|
+
const [inputValue, setInputValue] = useState("Mo");
|
|
327
|
+
|
|
328
|
+
const handleSelect = (label: string) => {
|
|
329
|
+
setInputValue(label);
|
|
330
|
+
setOpened(false);
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
return (
|
|
334
|
+
<div>
|
|
335
|
+
<Button
|
|
336
|
+
theme={EButtonTheme.SECONDARY}
|
|
337
|
+
size={EComponentSize.MD}
|
|
338
|
+
aria-haspopup="dialog"
|
|
339
|
+
aria-expanded={opened}
|
|
340
|
+
onClick={() => setOpened(true)}
|
|
341
|
+
>
|
|
342
|
+
Open
|
|
343
|
+
</Button>
|
|
344
|
+
<Puller opened={opened} setOpened={setOpened} role="dialog" aria-label="City search">
|
|
345
|
+
<PullerHeader
|
|
346
|
+
controlButtons={
|
|
347
|
+
<>
|
|
348
|
+
{/* В реальном сценарии лоадер показывается на время запроса подсказок. */}
|
|
349
|
+
<PullerLoader />
|
|
350
|
+
<PullerClose aria-label="Close" onClick={() => setOpened(false)} />
|
|
351
|
+
</>
|
|
352
|
+
}
|
|
353
|
+
>
|
|
354
|
+
<PullerInput
|
|
355
|
+
value={inputValue}
|
|
356
|
+
placeholder="Enter a city"
|
|
357
|
+
aria-label="City"
|
|
358
|
+
onChange={(event) => setInputValue(event.target.value)}
|
|
359
|
+
/>
|
|
360
|
+
</PullerHeader>
|
|
361
|
+
<PullerBody>
|
|
362
|
+
<PullerList aria-label="Suggestions">
|
|
363
|
+
{OPTIONS.map((option) => (
|
|
364
|
+
<PullerListItem key={option.id} id={option.id} onSelect={() => handleSelect(option.label)}>
|
|
365
|
+
{option.label}
|
|
366
|
+
</PullerListItem>
|
|
367
|
+
))}
|
|
368
|
+
</PullerList>
|
|
369
|
+
</PullerBody>
|
|
370
|
+
</Puller>
|
|
371
|
+
</div>
|
|
372
|
+
);
|
|
373
|
+
};
|
|
374
|
+
```
|
|
375
|
+
|