@reformer/ui-kit 7.0.0-beta.1 → 7.0.0-beta.2
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/dist/components/form-array/form-array-section.d.ts +44 -0
- package/dist/components/form-wizard/form-wizard-actions.d.ts +36 -0
- package/dist/components/form-wizard/form-wizard-progress.d.ts +30 -0
- package/dist/components/form-wizard/form-wizard.d.ts +107 -8
- package/dist/components/form-wizard/step-indicator.d.ts +27 -0
- package/dist/components/state/error-state.d.ts +21 -0
- package/dist/components/state/loading-state.d.ts +18 -0
- package/dist/components/ui/async-boundary.d.ts +4 -2
- package/dist/components/ui/box.d.ts +9 -5
- package/dist/components/ui/collapsible.d.ts +10 -6
- package/dist/components/ui/form-field.d.ts +26 -13
- package/dist/components/ui/input.d.ts +21 -0
- package/dist/components/ui/section.d.ts +9 -5
- package/dist/components/ui/select-resource.d.ts +176 -0
- package/dist/components/ui/select-resource.test.d.ts +1 -0
- package/dist/components/ui/select.d.ts +59 -60
- package/dist/index.d.ts +1 -1
- package/dist/ui/select.js +294 -141
- package/llms.txt +785 -250
- package/package.json +1 -1
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Чистая (React-free) логика асинхронного источника опций для {@link Select}.
|
|
3
|
+
*
|
|
4
|
+
* Вынесена из компонента, чтобы стратегии загрузки, слияние страниц и
|
|
5
|
+
* клиентская фильтрация были юнит-тестируемы без DOM (в монорепо тесты —
|
|
6
|
+
* логические `.test.ts`, без jsdom/testing-library).
|
|
7
|
+
*
|
|
8
|
+
* @module ui/select-resource
|
|
9
|
+
*/
|
|
10
|
+
/** Параметры запроса к {@link ResourceConfig.load}. */
|
|
11
|
+
export interface ResourceLoadParams {
|
|
12
|
+
/** Поисковый запрос (серверная фильтрация в стратегии `partial`). */
|
|
13
|
+
search?: string;
|
|
14
|
+
/** Номер страницы (1-based) для пагинации. */
|
|
15
|
+
page?: number;
|
|
16
|
+
/** Размер страницы. */
|
|
17
|
+
pageSize?: number;
|
|
18
|
+
/** Дополнительные пользовательские параметры. */
|
|
19
|
+
[key: string]: unknown;
|
|
20
|
+
}
|
|
21
|
+
/** Один элемент, возвращаемый {@link ResourceConfig.load}. */
|
|
22
|
+
export interface ResourceItem<T> {
|
|
23
|
+
/** Уникальный ключ элемента (для React `key` и дедупликации страниц). */
|
|
24
|
+
id: string | number;
|
|
25
|
+
/** Видимая подпись опции. */
|
|
26
|
+
label: string;
|
|
27
|
+
/** Значение, которое попадает в `onChange` (всегда приводится к строке). */
|
|
28
|
+
value: T;
|
|
29
|
+
/** Опциональное имя группы — варианты с одинаковым `group` объединяются в `SelectGroup`. */
|
|
30
|
+
group?: string;
|
|
31
|
+
/** Дополнительные поля бек-данных. */
|
|
32
|
+
[key: string]: unknown;
|
|
33
|
+
}
|
|
34
|
+
/** Ответ {@link ResourceConfig.load}. */
|
|
35
|
+
export interface ResourceResult<T> {
|
|
36
|
+
/** Список вариантов текущей страницы. */
|
|
37
|
+
items: ResourceItem<T>[];
|
|
38
|
+
/** Общее число доступных вариантов (для пагинации). */
|
|
39
|
+
totalCount: number;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Стратегия загрузки опций {@link Select}:
|
|
43
|
+
* - `'static'` — один `load({})` при монтировании; без поиска и пагинации (снимок);
|
|
44
|
+
* - `'preload'` — один `load({})` (сервер возвращает всё), поиск фильтрует
|
|
45
|
+
* загруженные опции на клиенте; пагинации нет;
|
|
46
|
+
* - `'partial'` — серверные поиск и пагинация: `load({ search, page })`,
|
|
47
|
+
* догрузка следующих страниц при скролле до `totalCount`.
|
|
48
|
+
*/
|
|
49
|
+
export type ResourceStrategy = 'static' | 'preload' | 'partial';
|
|
50
|
+
/** Конфигурация асинхронного источника опций для {@link Select}. */
|
|
51
|
+
export interface ResourceConfig<T> {
|
|
52
|
+
/** Стратегия загрузки. По умолчанию (если не задана) трактуется как `'static'`. */
|
|
53
|
+
type: ResourceStrategy;
|
|
54
|
+
/** Функция загрузки опций. Должна вернуть `{ items, totalCount }`. */
|
|
55
|
+
load: (params?: ResourceLoadParams) => Promise<ResourceResult<T>>;
|
|
56
|
+
/** Размер страницы для стратегии `partial`. По умолчанию 20. */
|
|
57
|
+
pageSize?: number;
|
|
58
|
+
}
|
|
59
|
+
/** Нормализованная опция: `value` приведён к строке для Radix и `onChange`. */
|
|
60
|
+
export interface NormalizedOption {
|
|
61
|
+
id: string | number;
|
|
62
|
+
label: string;
|
|
63
|
+
value: string;
|
|
64
|
+
group?: string;
|
|
65
|
+
}
|
|
66
|
+
/** Поведенческие флаги стратегии. */
|
|
67
|
+
export interface StrategyFlags {
|
|
68
|
+
/** Поиск уходит на сервер (`load({ search })`) vs фильтруется на клиенте. */
|
|
69
|
+
serverSearch: boolean;
|
|
70
|
+
/** Опции подгружаются постранично по мере скролла. */
|
|
71
|
+
paginated: boolean;
|
|
72
|
+
/** Показывать ли поле поиска в дропдауне. */
|
|
73
|
+
searchable: boolean;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Разложить {@link ResourceStrategy} в поведенческие флаги.
|
|
77
|
+
*
|
|
78
|
+
* @param type - стратегия (undefined трактуется как `'static'`)
|
|
79
|
+
* @returns флаги {@link StrategyFlags}
|
|
80
|
+
*/
|
|
81
|
+
export declare function resolveStrategyFlags(type: ResourceStrategy | undefined): StrategyFlags;
|
|
82
|
+
/**
|
|
83
|
+
* Привести «сырые» элементы ответа к {@link NormalizedOption} (`value` → строка).
|
|
84
|
+
*
|
|
85
|
+
* @param items - элементы из {@link ResourceResult.items}
|
|
86
|
+
* @returns нормализованные опции
|
|
87
|
+
*/
|
|
88
|
+
export declare function normalizeItems<T>(items: ResourceItem<T>[]): NormalizedOption[];
|
|
89
|
+
/**
|
|
90
|
+
* Слить ранее загруженные опции со страницей новых, отбрасывая дубли по `id`
|
|
91
|
+
* (сервер может вернуть пересечение при гонке страниц).
|
|
92
|
+
*
|
|
93
|
+
* @param prev - уже загруженные опции
|
|
94
|
+
* @param next - опции новой страницы
|
|
95
|
+
* @returns объединённый список без повторов `id`
|
|
96
|
+
*/
|
|
97
|
+
export declare function mergeOptions(prev: NormalizedOption[], next: NormalizedOption[]): NormalizedOption[];
|
|
98
|
+
/**
|
|
99
|
+
* Клиентская фильтрация опций по подстроке `label` (case-insensitive).
|
|
100
|
+
* Используется стратегией `preload`; пустой запрос возвращает исходный список.
|
|
101
|
+
*
|
|
102
|
+
* @param options - загруженные опции
|
|
103
|
+
* @param query - строка поиска
|
|
104
|
+
* @returns отфильтрованные опции
|
|
105
|
+
*/
|
|
106
|
+
export declare function filterClient(options: NormalizedOption[], query: string): NormalizedOption[];
|
|
107
|
+
/** Состояние загрузки асинхронного источника. */
|
|
108
|
+
export interface ResourceState {
|
|
109
|
+
/** Накопленные (по страницам) опции — уже нормализованные. */
|
|
110
|
+
options: NormalizedOption[];
|
|
111
|
+
/** Последняя загруженная страница (1-based). */
|
|
112
|
+
page: number;
|
|
113
|
+
/** Общее число опций по данным сервера. */
|
|
114
|
+
totalCount: number;
|
|
115
|
+
/** Первичная загрузка (маунт или смена запроса). */
|
|
116
|
+
loading: boolean;
|
|
117
|
+
/** Догрузка следующей страницы. */
|
|
118
|
+
loadingMore: boolean;
|
|
119
|
+
/** Последняя загрузка завершилась ошибкой. */
|
|
120
|
+
error: boolean;
|
|
121
|
+
/** Активный (применённый) поисковый запрос. */
|
|
122
|
+
search: string;
|
|
123
|
+
}
|
|
124
|
+
export type ResourceAction<T> = {
|
|
125
|
+
kind: 'load-start';
|
|
126
|
+
search: string;
|
|
127
|
+
} | {
|
|
128
|
+
kind: 'load-success';
|
|
129
|
+
result: ResourceResult<T>;
|
|
130
|
+
page: number;
|
|
131
|
+
} | {
|
|
132
|
+
kind: 'load-more-start';
|
|
133
|
+
} | {
|
|
134
|
+
kind: 'load-more-success';
|
|
135
|
+
result: ResourceResult<T>;
|
|
136
|
+
page: number;
|
|
137
|
+
} | {
|
|
138
|
+
kind: 'load-error';
|
|
139
|
+
more?: boolean;
|
|
140
|
+
} | {
|
|
141
|
+
kind: 'reset';
|
|
142
|
+
};
|
|
143
|
+
/** Начальное состояние источника. */
|
|
144
|
+
export declare function initialResourceState(): ResourceState;
|
|
145
|
+
/**
|
|
146
|
+
* Чистый reducer состояния асинхронного источника опций.
|
|
147
|
+
*
|
|
148
|
+
* - `load-start` сбрасывает список (новый запрос/маунт) и поднимает `loading`;
|
|
149
|
+
* - `load-success` заменяет опции первой страницей;
|
|
150
|
+
* - `load-more-*` аппендит следующую страницу (с дедупликацией по `id`);
|
|
151
|
+
* - `load-error` гасит флаги загрузки и ставит `error`.
|
|
152
|
+
*
|
|
153
|
+
* @param state - текущее состояние
|
|
154
|
+
* @param action - действие {@link ResourceAction}
|
|
155
|
+
* @returns новое состояние
|
|
156
|
+
*/
|
|
157
|
+
export declare function resourceReducer<T>(state: ResourceState, action: ResourceAction<T>): ResourceState;
|
|
158
|
+
/**
|
|
159
|
+
* Есть ли ещё непогруженные страницы (для infinite-scroll в стратегии `partial`).
|
|
160
|
+
*
|
|
161
|
+
* @param state - состояние источника
|
|
162
|
+
* @returns `true`, если загружено меньше опций, чем `totalCount`
|
|
163
|
+
*/
|
|
164
|
+
export declare function hasMore(state: ResourceState): boolean;
|
|
165
|
+
/**
|
|
166
|
+
* Достигнут ли порог подгрузки при прокрутке контейнера опций.
|
|
167
|
+
*
|
|
168
|
+
* @param el - метрики скролла контейнера
|
|
169
|
+
* @param threshold - запас в пикселях до низа (по умолчанию 48)
|
|
170
|
+
* @returns `true`, если пора грузить следующую страницу
|
|
171
|
+
*/
|
|
172
|
+
export declare function isNearBottom(el: {
|
|
173
|
+
scrollTop: number;
|
|
174
|
+
clientHeight: number;
|
|
175
|
+
scrollHeight: number;
|
|
176
|
+
}, threshold?: number): boolean;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,48 +1,7 @@
|
|
|
1
|
+
import { ResourceConfig } from './select-resource';
|
|
1
2
|
import * as React from 'react';
|
|
2
3
|
import * as SelectPrimitive from '@radix-ui/react-select';
|
|
3
|
-
|
|
4
|
-
interface ResourceLoadParams {
|
|
5
|
-
/** Поисковый запрос (для async-фильтрации). */
|
|
6
|
-
search?: string;
|
|
7
|
-
/** Номер страницы (1-based) для пагинации. */
|
|
8
|
-
page?: number;
|
|
9
|
-
/** Размер страницы. */
|
|
10
|
-
pageSize?: number;
|
|
11
|
-
/** Дополнительные пользовательские параметры. */
|
|
12
|
-
[key: string]: unknown;
|
|
13
|
-
}
|
|
14
|
-
/** Один элемент, возвращаемый {@link ResourceConfig.load}. */
|
|
15
|
-
interface ResourceItem<T> {
|
|
16
|
-
/** Уникальный ключ элемента (для React `key`). */
|
|
17
|
-
id: string | number;
|
|
18
|
-
/** Видимая подпись опции. */
|
|
19
|
-
label: string;
|
|
20
|
-
/** Значение, которое попадает в `onChange` (всегда приводится к строке). */
|
|
21
|
-
value: T;
|
|
22
|
-
/** Опциональное имя группы — варианты с одинаковым `group` объединяются в `SelectGroup`. */
|
|
23
|
-
group?: string;
|
|
24
|
-
/** Дополнительные поля бек-данных. */
|
|
25
|
-
[key: string]: unknown;
|
|
26
|
-
}
|
|
27
|
-
/** Ответ {@link ResourceConfig.load}. */
|
|
28
|
-
interface ResourceResult<T> {
|
|
29
|
-
/** Список вариантов. */
|
|
30
|
-
items: ResourceItem<T>[];
|
|
31
|
-
/** Общее число доступных вариантов (для пагинации). */
|
|
32
|
-
totalCount: number;
|
|
33
|
-
}
|
|
34
|
-
/** Конфигурация асинхронного источника опций для {@link Select}. */
|
|
35
|
-
export interface ResourceConfig<T> {
|
|
36
|
-
/**
|
|
37
|
-
* Стратегия загрузки:
|
|
38
|
-
* - `'static'` — варианты загружаются один раз и больше не меняются;
|
|
39
|
-
* - `'preload'` — загрузка на маунт компонента (используется по умолчанию в {@link Select});
|
|
40
|
-
* - `'partial'` — порционная загрузка (нужен будет внешний механизм пагинации).
|
|
41
|
-
*/
|
|
42
|
-
type: 'static' | 'preload' | 'partial';
|
|
43
|
-
/** Функция загрузки опций. Должна вернуть `{ items, totalCount }`. */
|
|
44
|
-
load: (params?: ResourceLoadParams) => Promise<ResourceResult<T>>;
|
|
45
|
-
}
|
|
4
|
+
export type { ResourceConfig, ResourceLoadParams, ResourceItem, ResourceResult, ResourceStrategy, NormalizedOption, } from './select-resource';
|
|
46
5
|
/** Props компонента {@link Select}. */
|
|
47
6
|
export interface SelectProps<T> extends Omit<React.ComponentProps<typeof SelectPrimitive.Root>, 'value' | 'onValueChange'> {
|
|
48
7
|
/** Дополнительный CSS-класс для триггера. */
|
|
@@ -72,9 +31,17 @@ export interface SelectProps<T> extends Omit<React.ComponentProps<typeof SelectP
|
|
|
72
31
|
clearable?: boolean;
|
|
73
32
|
}
|
|
74
33
|
/**
|
|
75
|
-
* Выпадающий список на `@radix-ui/react-select`.
|
|
76
|
-
*
|
|
77
|
-
*
|
|
34
|
+
* Выпадающий список на `@radix-ui/react-select`. Два источника данных: inline
|
|
35
|
+
* `options` и асинхронный `resource` со стратегией загрузки
|
|
36
|
+
* ({@link ResourceConfig.type}):
|
|
37
|
+
*
|
|
38
|
+
* - `static` — один `load({})` при маунте, без поиска;
|
|
39
|
+
* - `preload` — грузит всё сразу, поиск фильтрует опции на клиенте;
|
|
40
|
+
* - `partial` — серверные поиск (`load({ search })` с debounce) и пагинация
|
|
41
|
+
* (`load({ page })` по мере скролла до `totalCount`).
|
|
42
|
+
*
|
|
43
|
+
* Для `preload`/`partial` в дропдауне показывается поле поиска. При
|
|
44
|
+
* `clearable=true` рядом со значением есть крестик сброса в `null`.
|
|
78
45
|
*
|
|
79
46
|
* @example Inline-options
|
|
80
47
|
* ```tsx
|
|
@@ -87,24 +54,47 @@ export interface SelectProps<T> extends Omit<React.ComponentProps<typeof SelectP
|
|
|
87
54
|
* options={[
|
|
88
55
|
* { value: 'consumer', label: 'Потребительский' },
|
|
89
56
|
* { value: 'mortgage', label: 'Ипотека' },
|
|
90
|
-
* { value: 'auto', label: 'Авто' },
|
|
91
57
|
* ]}
|
|
92
58
|
* />
|
|
93
59
|
* ```
|
|
94
60
|
*
|
|
95
|
-
* @example
|
|
61
|
+
* @example Серверные поиск и пагинация (resource type='partial')
|
|
96
62
|
* ```tsx
|
|
97
|
-
* import { Select } from '@reformer/ui-kit';
|
|
63
|
+
* import { Select, type ResourceConfig } from '@reformer/ui-kit';
|
|
64
|
+
*
|
|
65
|
+
* const users: ResourceConfig<string> = {
|
|
66
|
+
* type: 'partial',
|
|
67
|
+
* pageSize: 20,
|
|
68
|
+
* load: async ({ search = '', page = 1, pageSize = 20 }) => {
|
|
69
|
+
* const res = await fetch(`/api/users?q=${search}&page=${page}&size=${pageSize}`);
|
|
70
|
+
* const { rows, total } = await res.json();
|
|
71
|
+
* return {
|
|
72
|
+
* items: rows.map((u) => ({ id: u.id, label: u.name, value: u.id })),
|
|
73
|
+
* totalCount: total,
|
|
74
|
+
* };
|
|
75
|
+
* },
|
|
76
|
+
* };
|
|
77
|
+
*
|
|
78
|
+
* <Select value={userId} onChange={setUserId} resource={users} clearable />
|
|
79
|
+
* ```
|
|
98
80
|
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
81
|
+
* @example Предзагрузка с клиентским поиском (resource type='preload')
|
|
82
|
+
* ```tsx
|
|
83
|
+
* import { Select, type ResourceConfig } from '@reformer/ui-kit';
|
|
84
|
+
*
|
|
85
|
+
* const countries: ResourceConfig<string> = {
|
|
86
|
+
* type: 'preload',
|
|
87
|
+
* load: async () => {
|
|
88
|
+
* const res = await fetch('/api/countries');
|
|
89
|
+
* const items = await res.json();
|
|
90
|
+
* return {
|
|
91
|
+
* items: items.map((c) => ({ id: c.id, label: c.name, value: c.code })),
|
|
92
|
+
* totalCount: items.length,
|
|
93
|
+
* };
|
|
94
|
+
* },
|
|
95
|
+
* };
|
|
96
|
+
*
|
|
97
|
+
* <Select value={country} onChange={setCountry} resource={countries} />
|
|
108
98
|
* ```
|
|
109
99
|
*/
|
|
110
100
|
declare const Select: React.ForwardRefExoticComponent<SelectProps<any> & {
|
|
@@ -199,10 +189,14 @@ declare function SelectTrigger({ className, size, children, 'aria-label': ariaLa
|
|
|
199
189
|
size?: 'sm' | 'default';
|
|
200
190
|
}): import("react/jsx-runtime").JSX.Element;
|
|
201
191
|
/**
|
|
202
|
-
*
|
|
192
|
+
* Дропдаун-контent {@link Select} (попап со списком). Обёртка над
|
|
203
193
|
* `Radix.Select.Content` с порталом, скролл-кнопками сверху/снизу и
|
|
204
194
|
* `position='popper'` по умолчанию (растягивается под ширину триггера).
|
|
205
195
|
*
|
|
196
|
+
* Опционально принимает `header` (нескроллящаяся шапка, например поле поиска)
|
|
197
|
+
* и `onViewportScroll` (для infinite-scroll — вешается на скроллящийся
|
|
198
|
+
* viewport со списком опций).
|
|
199
|
+
*
|
|
206
200
|
* @example Popper-режим (default — ширина равна триггеру)
|
|
207
201
|
* ```tsx
|
|
208
202
|
* import { SelectContent, SelectItem } from '@reformer/ui-kit/select';
|
|
@@ -222,7 +216,12 @@ declare function SelectTrigger({ className, size, children, 'aria-label': ariaLa
|
|
|
222
216
|
* </SelectContent>
|
|
223
217
|
* ```
|
|
224
218
|
*/
|
|
225
|
-
declare function SelectContent({ className, children, position, ...props }: React.ComponentProps<typeof SelectPrimitive.Content>
|
|
219
|
+
declare function SelectContent({ className, children, position, header, onViewportScroll, ...props }: React.ComponentProps<typeof SelectPrimitive.Content> & {
|
|
220
|
+
/** Нескроллящаяся шапка над списком (например, поле поиска). */
|
|
221
|
+
header?: React.ReactNode;
|
|
222
|
+
/** Обработчик прокрутки viewport-а со списком (для infinite-scroll). */
|
|
223
|
+
onViewportScroll?: React.UIEventHandler<HTMLDivElement>;
|
|
224
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
226
225
|
/**
|
|
227
226
|
* Заголовок секции внутри {@link SelectGroup}. Обёртка над
|
|
228
227
|
* `Radix.Select.Label`. Не выбирается; используется для визуального разделения
|
package/dist/index.d.ts
CHANGED
|
@@ -22,7 +22,7 @@ export type { RadioGroupProps, RadioOption } from './components/ui/radio-group';
|
|
|
22
22
|
export { Section } from './components/ui/section';
|
|
23
23
|
export type { SectionProps } from './components/ui/section';
|
|
24
24
|
export { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectTrigger, SelectValue, } from './components/ui/select';
|
|
25
|
-
export type { SelectProps } from './components/ui/select';
|
|
25
|
+
export type { SelectProps, ResourceConfig } from './components/ui/select';
|
|
26
26
|
export { Textarea } from './components/ui/textarea';
|
|
27
27
|
export type { TextareaProps } from './components/ui/textarea';
|
|
28
28
|
export { ErrorState } from './components/state/error-state';
|