@astral/ui 4.93.0 → 4.95.0
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/components/AsyncAutocomplete/AsyncAutocomplete.d.ts +1 -0
- package/components/AsyncAutocomplete/types.d.ts +5 -0
- package/components/AsyncAutocomplete/useLogic/useLogic.d.ts +1 -1
- package/components/AsyncAutocomplete/useLogic/useLogic.js +13 -4
- package/components/useAsyncOptions/faker.d.ts +3 -0
- package/components/useAsyncOptions/faker.js +3 -0
- package/components/useAsyncOptions/index.d.ts +1 -0
- package/components/useAsyncOptions/index.js +1 -0
- package/components/useAsyncOptions/public.d.ts +1 -0
- package/components/useAsyncOptions/public.js +1 -0
- package/components/useAsyncOptions/types.d.ts +7 -0
- package/components/useAsyncOptions/types.js +1 -0
- package/components/useAsyncOptions/useAsyncOptions.d.ts +8 -1
- package/components/useAsyncOptions/useAsyncOptions.js +22 -3
- package/hook-form/FormPageLayout/FormPageFooter/FormPageFooter.d.ts +5 -0
- package/hook-form/FormPageLayout/FormPageFooter/FormPageFooter.js +2 -2
- package/hook-form/FormPageLayout/FormPageLayout.d.ts +1 -25
- package/hook-form/FormPageLayout/FormPageLayout.js +2 -2
- package/hook-form/FormPageLayout/index.d.ts +1 -0
- package/hook-form/FormPageLayout/public.d.ts +1 -0
- package/hook-form/FormPageLayout/types.d.ts +31 -0
- package/hook-form/FormPageLayout/types.js +1 -0
- package/node/components/AsyncAutocomplete/AsyncAutocomplete.d.ts +1 -0
- package/node/components/AsyncAutocomplete/types.d.ts +5 -0
- package/node/components/AsyncAutocomplete/useLogic/useLogic.d.ts +1 -1
- package/node/components/AsyncAutocomplete/useLogic/useLogic.js +13 -4
- package/node/components/useAsyncOptions/faker.d.ts +3 -0
- package/node/components/useAsyncOptions/faker.js +9 -0
- package/node/components/useAsyncOptions/index.d.ts +1 -0
- package/node/components/useAsyncOptions/index.js +1 -0
- package/node/components/useAsyncOptions/public.d.ts +1 -0
- package/node/components/useAsyncOptions/public.js +1 -0
- package/node/components/useAsyncOptions/types.d.ts +7 -0
- package/node/components/useAsyncOptions/types.js +2 -0
- package/node/components/useAsyncOptions/useAsyncOptions.d.ts +8 -1
- package/node/components/useAsyncOptions/useAsyncOptions.js +22 -3
- package/node/hook-form/FormPageLayout/FormPageFooter/FormPageFooter.d.ts +5 -0
- package/node/hook-form/FormPageLayout/FormPageFooter/FormPageFooter.js +2 -2
- package/node/hook-form/FormPageLayout/FormPageLayout.d.ts +1 -25
- package/node/hook-form/FormPageLayout/FormPageLayout.js +2 -2
- package/node/hook-form/FormPageLayout/index.d.ts +1 -0
- package/node/hook-form/FormPageLayout/public.d.ts +1 -0
- package/node/hook-form/FormPageLayout/types.d.ts +31 -0
- package/node/hook-form/FormPageLayout/types.js +2 -0
- package/package.json +1 -1
- package/components/AsyncAutocomplete/faker.d.ts +0 -2
- package/components/AsyncAutocomplete/faker.js +0 -38
- package/node/components/AsyncAutocomplete/faker.d.ts +0 -2
- package/node/components/AsyncAutocomplete/faker.js +0 -45
|
@@ -9,5 +9,6 @@ export declare const AsyncAutocomplete: <TOptions, TMultiple extends boolean = f
|
|
|
9
9
|
fetchDelayMs?: number | undefined;
|
|
10
10
|
fetchOptions: (search: string, signal?: AbortSignal | undefined) => Promise<TOptions[]>;
|
|
11
11
|
minSymbolsToFetch?: number | undefined;
|
|
12
|
+
shouldFetch?: import("../useAsyncOptions").ShouldFetch | undefined;
|
|
12
13
|
onDataLoadError?: ((error: Error) => void) | undefined;
|
|
13
14
|
} & import("react").RefAttributes<unknown>) => import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | null;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type AutocompleteProps } from '../Autocomplete';
|
|
2
|
+
import { type ShouldFetch } from '../useAsyncOptions';
|
|
2
3
|
export type AsyncAutocompleteProps<TOptions, TMultiple extends boolean = false, TDisableClearable extends boolean = false, TFreeSolo extends boolean = false> = Omit<AutocompleteProps<TOptions, TMultiple, TDisableClearable, TFreeSolo>, 'inputValue' | 'loading' | 'disabled' | 'fullWidth' | 'error' | 'required' | 'success' | 'options' | 'filterOptions'> & {
|
|
3
4
|
/**
|
|
4
5
|
* Если `true`, будет показан индикатор загрузки
|
|
@@ -59,6 +60,10 @@ export type AsyncAutocompleteProps<TOptions, TMultiple extends boolean = false,
|
|
|
59
60
|
* @default 0
|
|
60
61
|
*/
|
|
61
62
|
minSymbolsToFetch?: number;
|
|
63
|
+
/**
|
|
64
|
+
* Функция, задающая правила валидации запроса
|
|
65
|
+
*/
|
|
66
|
+
shouldFetch?: ShouldFetch;
|
|
62
67
|
/**
|
|
63
68
|
* Функция для обработки ошибок загрузки данных
|
|
64
69
|
*/
|
|
@@ -2,7 +2,7 @@ import type { AutocompleteInputChangeReason } from '@mui/material/Autocomplete';
|
|
|
2
2
|
import { type SyntheticEvent } from 'react';
|
|
3
3
|
import { type AsyncAutocompleteProps } from '../types';
|
|
4
4
|
type UseLogicParams<TOptions, TMultiple extends boolean, TDisableClearable extends boolean, TFreeSolo extends boolean> = AsyncAutocompleteProps<TOptions, TMultiple, TDisableClearable, TFreeSolo>;
|
|
5
|
-
export declare const useLogic: <TOptions, TMultiple extends boolean, TDisableClearable extends boolean, TFreeSolo extends boolean>({ fetchOptions, fetchDelayMs, minSymbolsToFetch, defaultValue, getOptionLabel, noOptionsText, onOpen, onInputChange, onDataLoadError, isLoadedDataError, isDisabled, isFullWidth, isSuccess, isError, isLoading, isRequired, ...restProps }: UseLogicParams<TOptions, TMultiple, TDisableClearable, TFreeSolo>) => {
|
|
5
|
+
export declare const useLogic: <TOptions, TMultiple extends boolean, TDisableClearable extends boolean, TFreeSolo extends boolean>({ fetchOptions, fetchDelayMs, minSymbolsToFetch, shouldFetch, defaultValue, getOptionLabel, noOptionsText, onOpen, onInputChange, onDataLoadError, isLoadedDataError, isDisabled, isFullWidth, isSuccess, isError, isLoading, isRequired, ...restProps }: UseLogicParams<TOptions, TMultiple, TDisableClearable, TFreeSolo>) => {
|
|
6
6
|
autocompleteProps: {
|
|
7
7
|
classes?: Partial<import("@mui/material/Autocomplete").AutocompleteClasses> | undefined;
|
|
8
8
|
defaultChecked?: boolean | undefined;
|
|
@@ -3,11 +3,12 @@ import { DEFAULT_DELAY_MS, DEFAULT_EMPTY_OPTIONS_TEXT, DEFAULT_MIN_SEARCH_CHARAC
|
|
|
3
3
|
/* Стандартный фильтр. Необходим для правильного отображения опций, при асинхронном поиске
|
|
4
4
|
* см. https://track.astral.ru/soft/browse/UIKIT-2823 */
|
|
5
5
|
const filterOptions = (options) => options;
|
|
6
|
-
export const useLogic = ({ fetchOptions, fetchDelayMs = DEFAULT_DELAY_MS, minSymbolsToFetch = DEFAULT_MIN_SEARCH_CHARACTERS, defaultValue, getOptionLabel, noOptionsText = DEFAULT_NO_OPTIONS_TEXT, onOpen, onInputChange, onDataLoadError, isLoadedDataError, isDisabled, isFullWidth, isSuccess, isError, isLoading, isRequired, ...restProps }) => {
|
|
7
|
-
const { options, onInputChange: propsOnInputChange, onOpen: propsOnOpen, isLoadingData, isLoadingError, } = useAsyncOptions({
|
|
6
|
+
export const useLogic = ({ fetchOptions, fetchDelayMs = DEFAULT_DELAY_MS, minSymbolsToFetch = DEFAULT_MIN_SEARCH_CHARACTERS, shouldFetch, defaultValue, getOptionLabel, noOptionsText = DEFAULT_NO_OPTIONS_TEXT, onOpen, onInputChange, onDataLoadError, isLoadedDataError, isDisabled, isFullWidth, isSuccess, isError, isLoading, isRequired, ...restProps }) => {
|
|
7
|
+
const { options, onInputChange: propsOnInputChange, onOpen: propsOnOpen, isLoadingData, isLoadingError, isAllow, hintText, } = useAsyncOptions({
|
|
8
8
|
fetchOptions,
|
|
9
9
|
fetchDelayMs,
|
|
10
10
|
minSymbolsToFetch,
|
|
11
|
+
shouldFetch,
|
|
11
12
|
onDataLoadError,
|
|
12
13
|
});
|
|
13
14
|
const handleInputChange = (event, search, reason) => {
|
|
@@ -21,7 +22,15 @@ export const useLogic = ({ fetchOptions, fetchDelayMs = DEFAULT_DELAY_MS, minSym
|
|
|
21
22
|
propsOnOpen(defaultSearch, defaultValue);
|
|
22
23
|
onOpen?.(event);
|
|
23
24
|
};
|
|
24
|
-
const
|
|
25
|
+
const getEmptyOptionsText = () => {
|
|
26
|
+
if (isAllow) {
|
|
27
|
+
return noOptionsText;
|
|
28
|
+
}
|
|
29
|
+
if (hintText !== '') {
|
|
30
|
+
return hintText;
|
|
31
|
+
}
|
|
32
|
+
return DEFAULT_EMPTY_OPTIONS_TEXT;
|
|
33
|
+
};
|
|
25
34
|
return {
|
|
26
35
|
autocompleteProps: {
|
|
27
36
|
options,
|
|
@@ -32,7 +41,7 @@ export const useLogic = ({ fetchOptions, fetchDelayMs = DEFAULT_DELAY_MS, minSym
|
|
|
32
41
|
required: isRequired,
|
|
33
42
|
success: isSuccess,
|
|
34
43
|
onOpen: handleOpen,
|
|
35
|
-
noOptionsText:
|
|
44
|
+
noOptionsText: getEmptyOptionsText(),
|
|
36
45
|
onInputChange: handleInputChange,
|
|
37
46
|
isLoadedDataError: isLoadedDataError ?? isLoadingError,
|
|
38
47
|
getOptionLabel,
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export declare const makeFetchOptions: (options: string[]) => import("@vitest/spy").Mock<import("@vitest/spy").Procedure>;
|
|
2
|
+
export declare const makeEmptyFetchOptions: () => import("@vitest/spy").Mock<import("@vitest/spy").Procedure>;
|
|
3
|
+
export declare const makeErrorFetchOptions: (error: Error) => import("@vitest/spy").Mock<import("@vitest/spy").Procedure>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AutocompleteInputChangeReason } from '@mui/material/Autocomplete';
|
|
2
|
+
import { type ShouldFetch } from './types';
|
|
2
3
|
type FetchResult<TOptions> = {
|
|
3
4
|
options: TOptions[];
|
|
4
5
|
meta?: {
|
|
@@ -37,6 +38,10 @@ type UseAsyncOptionsParams<TOptions> = {
|
|
|
37
38
|
* Минимальное количество символов для выполнения запроса
|
|
38
39
|
*/
|
|
39
40
|
minSymbolsToFetch: number;
|
|
41
|
+
/**
|
|
42
|
+
* Функция, задающая правила валидации запроса
|
|
43
|
+
*/
|
|
44
|
+
shouldFetch?: ShouldFetch;
|
|
40
45
|
/**
|
|
41
46
|
* Задержка для debounce
|
|
42
47
|
*/
|
|
@@ -57,7 +62,7 @@ type UseAsyncOptionsParams<TOptions> = {
|
|
|
57
62
|
* Хук для асинхронной загрузки опций в компонентах с поиском.
|
|
58
63
|
* Управляет задержкой, отменой запросов и состояниями загрузки.
|
|
59
64
|
*/
|
|
60
|
-
export declare const useAsyncOptions: <TOptions>({ fetchOptions, fetchDelayMs, minSymbolsToFetch, onDataLoadError, isSmoothLoading, }: UseAsyncOptionsParams<TOptions>) => {
|
|
65
|
+
export declare const useAsyncOptions: <TOptions>({ fetchOptions, fetchDelayMs, minSymbolsToFetch, shouldFetch, onDataLoadError, isSmoothLoading, }: UseAsyncOptionsParams<TOptions>) => {
|
|
61
66
|
options: TOptions[];
|
|
62
67
|
meta: {
|
|
63
68
|
isAllDataLoaded: boolean;
|
|
@@ -65,6 +70,8 @@ export declare const useAsyncOptions: <TOptions>({ fetchOptions, fetchDelayMs, m
|
|
|
65
70
|
isLoadingData: boolean;
|
|
66
71
|
onRetry: (search: string) => void;
|
|
67
72
|
isLoadingError: boolean;
|
|
73
|
+
isAllow: boolean;
|
|
74
|
+
hintText: string;
|
|
68
75
|
debouncedFetchOptions: import("debounce").DebouncedFunction<(search: string) => void>;
|
|
69
76
|
onInputChange: (search: string, reason?: AutocompleteInputChangeReason) => void;
|
|
70
77
|
onOpen: (defaultSearch?: string, defaultValue?: TOptions | null | undefined, isAlwaysTriggerOnOpen?: boolean) => void;
|
|
@@ -5,12 +5,14 @@ import { useLoadingState } from '../useLoadingState';
|
|
|
5
5
|
* Хук для асинхронной загрузки опций в компонентах с поиском.
|
|
6
6
|
* Управляет задержкой, отменой запросов и состояниями загрузки.
|
|
7
7
|
*/
|
|
8
|
-
export const useAsyncOptions = ({ fetchOptions, fetchDelayMs, minSymbolsToFetch, onDataLoadError, isSmoothLoading, }) => {
|
|
8
|
+
export const useAsyncOptions = ({ fetchOptions, fetchDelayMs, minSymbolsToFetch, shouldFetch, onDataLoadError, isSmoothLoading, }) => {
|
|
9
9
|
const [isLoadingData, setIsLoadingData] = useState(false);
|
|
10
10
|
const [isFirstOpen, setIsFirstOpen] = useState(true);
|
|
11
11
|
const [isLoadingError, setIsLoadingError] = useState(false);
|
|
12
12
|
const abortControllerRef = useRef(null);
|
|
13
13
|
const { isShowLoader } = useLoadingState(isLoadingData);
|
|
14
|
+
const [isAllow, setIsAllow] = useState(true);
|
|
15
|
+
const [hintText, setHintText] = useState('');
|
|
14
16
|
const [options, setOptions] = useState({
|
|
15
17
|
options: [],
|
|
16
18
|
});
|
|
@@ -39,17 +41,32 @@ export const useAsyncOptions = ({ fetchOptions, fetchDelayMs, minSymbolsToFetch,
|
|
|
39
41
|
abortControllerRef.current = null;
|
|
40
42
|
});
|
|
41
43
|
}, fetchDelayMs), [fetchOptions, fetchDelayMs]);
|
|
42
|
-
const
|
|
44
|
+
const isAllowedSearch = (search) => {
|
|
43
45
|
if ((!search.length && minSymbolsToFetch > 0) ||
|
|
44
46
|
search.length < minSymbolsToFetch) {
|
|
47
|
+
setHintText('');
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
const shouldFetchResult = shouldFetch?.(search);
|
|
51
|
+
if (shouldFetchResult && !shouldFetchResult.isAllow) {
|
|
52
|
+
setHintText(search.length !== 0 ? shouldFetchResult.reason : '');
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
return true;
|
|
56
|
+
};
|
|
57
|
+
const onInputChange = (search, reason) => {
|
|
58
|
+
if (!isAllowedSearch(search)) {
|
|
45
59
|
if (debouncedFetchOptions.isPending) {
|
|
46
60
|
debouncedFetchOptions.clear();
|
|
47
61
|
}
|
|
48
62
|
setIsLoadingData(false);
|
|
49
63
|
setOptions({ options: [] });
|
|
64
|
+
setIsAllow(false);
|
|
50
65
|
return;
|
|
51
66
|
}
|
|
52
67
|
if (!isFirstOpen) {
|
|
68
|
+
setIsAllow(true);
|
|
69
|
+
setHintText('');
|
|
53
70
|
if (reason === 'input') {
|
|
54
71
|
setIsLoadingData(true);
|
|
55
72
|
}
|
|
@@ -59,7 +76,7 @@ export const useAsyncOptions = ({ fetchOptions, fetchDelayMs, minSymbolsToFetch,
|
|
|
59
76
|
const onOpen = (defaultSearch = '', defaultValue, isAlwaysTriggerOnOpen = false) => {
|
|
60
77
|
if (isFirstOpen || isAlwaysTriggerOnOpen) {
|
|
61
78
|
setIsFirstOpen(false);
|
|
62
|
-
if (defaultValue ||
|
|
79
|
+
if (defaultValue || isAllowedSearch(defaultSearch)) {
|
|
63
80
|
setIsLoadingData(true);
|
|
64
81
|
debouncedFetchOptions(defaultSearch);
|
|
65
82
|
}
|
|
@@ -78,6 +95,8 @@ export const useAsyncOptions = ({ fetchOptions, fetchDelayMs, minSymbolsToFetch,
|
|
|
78
95
|
isLoadingData: isSmoothLoading ? isShowLoader : isLoadingData,
|
|
79
96
|
onRetry,
|
|
80
97
|
isLoadingError,
|
|
98
|
+
isAllow,
|
|
99
|
+
hintText,
|
|
81
100
|
debouncedFetchOptions,
|
|
82
101
|
onInputChange,
|
|
83
102
|
onOpen,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { SecondaryAction } from '../../FormPageLayoutBase';
|
|
2
|
+
import { type FormPageLayoutSubmitButtonProps } from '../types';
|
|
2
3
|
export type FormFooterProps = {
|
|
3
4
|
/**
|
|
4
5
|
* Если true, кнопки будут недоступны для взаимодействия
|
|
@@ -13,5 +14,9 @@ export type FormFooterProps = {
|
|
|
13
14
|
* @default 'Отправить'
|
|
14
15
|
*/
|
|
15
16
|
submitButtonText?: string;
|
|
17
|
+
/**
|
|
18
|
+
* Параметры для кнопки отправки формы
|
|
19
|
+
*/
|
|
20
|
+
submitButtonProps?: FormPageLayoutSubmitButtonProps;
|
|
16
21
|
};
|
|
17
22
|
export declare const FormPageFooter: (props: FormFooterProps) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -4,9 +4,9 @@ import { useFormFooter } from './hooks';
|
|
|
4
4
|
import { SecondaryActions } from './SecondaryActions';
|
|
5
5
|
import { ActionsWrapper, ShowMoreButton, StyledFormSubmitButton, } from './styles';
|
|
6
6
|
export const FormPageFooter = (props) => {
|
|
7
|
-
const { isDisabled, submitButtonText = 'Отправить', secondaryActions, } = props;
|
|
7
|
+
const { isDisabled, submitButtonText = 'Отправить', secondaryActions, submitButtonProps, } = props;
|
|
8
8
|
const { isActive, handleToggle, isShowMoreButton } = useFormFooter({
|
|
9
9
|
secondaryActions,
|
|
10
10
|
});
|
|
11
|
-
return (_jsxs(FormPageFooterContainer, { children: [_jsxs(ActionsWrapper, { "$isShowMoreButton": isShowMoreButton, children: [_jsx(StyledFormSubmitButton, { size: "large", disabled: isDisabled, children: submitButtonText }), secondaryActions && (_jsx(SecondaryActions, { isDisabled: isDisabled, actions: secondaryActions, isActive: isActive }))] }), isShowMoreButton && (_jsxs(ShowMoreButton, { disabled: isDisabled, variant: "text", onClick: handleToggle, "$isActive": isActive, children: [isActive ? 'Скрыть' : 'Показать', " \u0434\u0440\u0443\u0433\u0438\u0435 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044F"] }))] }));
|
|
11
|
+
return (_jsxs(FormPageFooterContainer, { children: [_jsxs(ActionsWrapper, { "$isShowMoreButton": isShowMoreButton, children: [_jsx(StyledFormSubmitButton, { size: "large", disabled: isDisabled, ...submitButtonProps, children: submitButtonText }), secondaryActions && (_jsx(SecondaryActions, { isDisabled: isDisabled, actions: secondaryActions, isActive: isActive }))] }), isShowMoreButton && (_jsxs(ShowMoreButton, { disabled: isDisabled, variant: "text", onClick: handleToggle, "$isActive": isActive, children: [isActive ? 'Скрыть' : 'Показать', " \u0434\u0440\u0443\u0433\u0438\u0435 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044F"] }))] }));
|
|
12
12
|
};
|
|
@@ -1,29 +1,5 @@
|
|
|
1
|
-
import { type
|
|
2
|
-
import { type PageLayoutProps } from '../../components/PageLayout';
|
|
3
|
-
import { type FormProps } from '../Form';
|
|
4
|
-
import type { SecondaryAction } from '../FormPageLayoutBase';
|
|
5
|
-
type FormPageProps<TFormValues extends Record<string, unknown>> = Omit<FormProps<TFormValues>, 'children'> & {
|
|
6
|
-
/**
|
|
7
|
-
* Текст для кнопки отправки формы
|
|
8
|
-
*/
|
|
9
|
-
submitButtonText?: string;
|
|
10
|
-
/**
|
|
11
|
-
* Дополнительные кнопки формы
|
|
12
|
-
*/
|
|
13
|
-
secondaryActions?: SecondaryAction[];
|
|
14
|
-
};
|
|
15
|
-
export type FormPageLayoutProps<TFormValues extends Record<string, unknown>> = Omit<PageLayoutProps, 'content'> & {
|
|
16
|
-
/**
|
|
17
|
-
* Содержимое формы
|
|
18
|
-
*/
|
|
19
|
-
children: ReactNode;
|
|
20
|
-
/**
|
|
21
|
-
* Параметры страницы формы
|
|
22
|
-
*/
|
|
23
|
-
form: FormPageProps<TFormValues>;
|
|
24
|
-
};
|
|
1
|
+
import { type FormPageLayoutProps } from './types';
|
|
25
2
|
/**
|
|
26
3
|
* FormPageLayout предназначен для построения страниц с формами.
|
|
27
4
|
*/
|
|
28
5
|
export declare const FormPageLayout: <TFormValues extends Record<string, unknown>>(props: FormPageLayoutProps<TFormValues>) => import("react/jsx-runtime").JSX.Element;
|
|
29
|
-
export {};
|
|
@@ -9,8 +9,8 @@ import { FormContent } from './styles';
|
|
|
9
9
|
*/
|
|
10
10
|
export const FormPageLayout = (props) => {
|
|
11
11
|
const { form, children, ...restProps } = props;
|
|
12
|
-
const { secondaryActions, submitButtonText, ...restFormProps } = form;
|
|
12
|
+
const { secondaryActions, submitButtonText, submitButtonProps, ...restFormProps } = form;
|
|
13
13
|
return (_jsx(FormPageLayoutBase, { ...restProps, formProps: { ...form, secondaryActions }, contentClassname: cn({
|
|
14
14
|
[formPageContentClassnames.formPageContentSecondaryActions]: Boolean(secondaryActions),
|
|
15
|
-
}), children: ({ isDisabled }) => (_jsxs(Form, { ...restFormProps, isDisabled: isDisabled, children: [_jsx(FormContent, { children: children }), _jsx(FormPageFooter, { isDisabled: isDisabled, submitButtonText: submitButtonText, secondaryActions: secondaryActions })] })) }));
|
|
15
|
+
}), children: ({ isDisabled }) => (_jsxs(Form, { ...restFormProps, isDisabled: isDisabled, children: [_jsx(FormContent, { children: children }), _jsx(FormPageFooter, { isDisabled: isDisabled, submitButtonText: submitButtonText, secondaryActions: secondaryActions, submitButtonProps: submitButtonProps })] })) }));
|
|
16
16
|
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import { type PageLayoutProps } from '../../components/PageLayout';
|
|
3
|
+
import { type FormProps } from '../Form';
|
|
4
|
+
import { type SecondaryAction } from '../FormPageLayoutBase';
|
|
5
|
+
import { type FormSubmitButtonProps } from '../FormSubmitButton';
|
|
6
|
+
export type FormPageLayoutSubmitButtonProps = Pick<FormSubmitButtonProps, 'color' | 'startIcon' | 'endIcon'>;
|
|
7
|
+
type FormPageProps<TFormValues extends Record<string, unknown>> = Omit<FormProps<TFormValues>, 'children'> & {
|
|
8
|
+
/**
|
|
9
|
+
* Параметры для кнопки отправки формы
|
|
10
|
+
*/
|
|
11
|
+
submitButtonProps?: FormPageLayoutSubmitButtonProps;
|
|
12
|
+
/**
|
|
13
|
+
* Текст для кнопки отправки формы
|
|
14
|
+
*/
|
|
15
|
+
submitButtonText?: string;
|
|
16
|
+
/**
|
|
17
|
+
* Дополнительные кнопки формы
|
|
18
|
+
*/
|
|
19
|
+
secondaryActions?: SecondaryAction[];
|
|
20
|
+
};
|
|
21
|
+
export type FormPageLayoutProps<TFormValues extends Record<string, unknown>> = Omit<PageLayoutProps, 'content'> & {
|
|
22
|
+
/**
|
|
23
|
+
* Содержимое формы
|
|
24
|
+
*/
|
|
25
|
+
children: ReactNode;
|
|
26
|
+
/**
|
|
27
|
+
* Параметры страницы формы
|
|
28
|
+
*/
|
|
29
|
+
form: FormPageProps<TFormValues>;
|
|
30
|
+
};
|
|
31
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -9,5 +9,6 @@ export declare const AsyncAutocomplete: <TOptions, TMultiple extends boolean = f
|
|
|
9
9
|
fetchDelayMs?: number | undefined;
|
|
10
10
|
fetchOptions: (search: string, signal?: AbortSignal | undefined) => Promise<TOptions[]>;
|
|
11
11
|
minSymbolsToFetch?: number | undefined;
|
|
12
|
+
shouldFetch?: import("../useAsyncOptions").ShouldFetch | undefined;
|
|
12
13
|
onDataLoadError?: ((error: Error) => void) | undefined;
|
|
13
14
|
} & import("react").RefAttributes<unknown>) => import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | null;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type AutocompleteProps } from '../Autocomplete';
|
|
2
|
+
import { type ShouldFetch } from '../useAsyncOptions';
|
|
2
3
|
export type AsyncAutocompleteProps<TOptions, TMultiple extends boolean = false, TDisableClearable extends boolean = false, TFreeSolo extends boolean = false> = Omit<AutocompleteProps<TOptions, TMultiple, TDisableClearable, TFreeSolo>, 'inputValue' | 'loading' | 'disabled' | 'fullWidth' | 'error' | 'required' | 'success' | 'options' | 'filterOptions'> & {
|
|
3
4
|
/**
|
|
4
5
|
* Если `true`, будет показан индикатор загрузки
|
|
@@ -59,6 +60,10 @@ export type AsyncAutocompleteProps<TOptions, TMultiple extends boolean = false,
|
|
|
59
60
|
* @default 0
|
|
60
61
|
*/
|
|
61
62
|
minSymbolsToFetch?: number;
|
|
63
|
+
/**
|
|
64
|
+
* Функция, задающая правила валидации запроса
|
|
65
|
+
*/
|
|
66
|
+
shouldFetch?: ShouldFetch;
|
|
62
67
|
/**
|
|
63
68
|
* Функция для обработки ошибок загрузки данных
|
|
64
69
|
*/
|
|
@@ -2,7 +2,7 @@ import type { AutocompleteInputChangeReason } from '@mui/material/Autocomplete';
|
|
|
2
2
|
import { type SyntheticEvent } from 'react';
|
|
3
3
|
import { type AsyncAutocompleteProps } from '../types';
|
|
4
4
|
type UseLogicParams<TOptions, TMultiple extends boolean, TDisableClearable extends boolean, TFreeSolo extends boolean> = AsyncAutocompleteProps<TOptions, TMultiple, TDisableClearable, TFreeSolo>;
|
|
5
|
-
export declare const useLogic: <TOptions, TMultiple extends boolean, TDisableClearable extends boolean, TFreeSolo extends boolean>({ fetchOptions, fetchDelayMs, minSymbolsToFetch, defaultValue, getOptionLabel, noOptionsText, onOpen, onInputChange, onDataLoadError, isLoadedDataError, isDisabled, isFullWidth, isSuccess, isError, isLoading, isRequired, ...restProps }: UseLogicParams<TOptions, TMultiple, TDisableClearable, TFreeSolo>) => {
|
|
5
|
+
export declare const useLogic: <TOptions, TMultiple extends boolean, TDisableClearable extends boolean, TFreeSolo extends boolean>({ fetchOptions, fetchDelayMs, minSymbolsToFetch, shouldFetch, defaultValue, getOptionLabel, noOptionsText, onOpen, onInputChange, onDataLoadError, isLoadedDataError, isDisabled, isFullWidth, isSuccess, isError, isLoading, isRequired, ...restProps }: UseLogicParams<TOptions, TMultiple, TDisableClearable, TFreeSolo>) => {
|
|
6
6
|
autocompleteProps: {
|
|
7
7
|
classes?: Partial<import("@mui/material/Autocomplete").AutocompleteClasses> | undefined;
|
|
8
8
|
defaultChecked?: boolean | undefined;
|
|
@@ -6,11 +6,12 @@ const constants_1 = require("../constants");
|
|
|
6
6
|
/* Стандартный фильтр. Необходим для правильного отображения опций, при асинхронном поиске
|
|
7
7
|
* см. https://track.astral.ru/soft/browse/UIKIT-2823 */
|
|
8
8
|
const filterOptions = (options) => options;
|
|
9
|
-
const useLogic = ({ fetchOptions, fetchDelayMs = constants_1.DEFAULT_DELAY_MS, minSymbolsToFetch = constants_1.DEFAULT_MIN_SEARCH_CHARACTERS, defaultValue, getOptionLabel, noOptionsText = constants_1.DEFAULT_NO_OPTIONS_TEXT, onOpen, onInputChange, onDataLoadError, isLoadedDataError, isDisabled, isFullWidth, isSuccess, isError, isLoading, isRequired, ...restProps }) => {
|
|
10
|
-
const { options, onInputChange: propsOnInputChange, onOpen: propsOnOpen, isLoadingData, isLoadingError, } = (0, useAsyncOptions_1.useAsyncOptions)({
|
|
9
|
+
const useLogic = ({ fetchOptions, fetchDelayMs = constants_1.DEFAULT_DELAY_MS, minSymbolsToFetch = constants_1.DEFAULT_MIN_SEARCH_CHARACTERS, shouldFetch, defaultValue, getOptionLabel, noOptionsText = constants_1.DEFAULT_NO_OPTIONS_TEXT, onOpen, onInputChange, onDataLoadError, isLoadedDataError, isDisabled, isFullWidth, isSuccess, isError, isLoading, isRequired, ...restProps }) => {
|
|
10
|
+
const { options, onInputChange: propsOnInputChange, onOpen: propsOnOpen, isLoadingData, isLoadingError, isAllow, hintText, } = (0, useAsyncOptions_1.useAsyncOptions)({
|
|
11
11
|
fetchOptions,
|
|
12
12
|
fetchDelayMs,
|
|
13
13
|
minSymbolsToFetch,
|
|
14
|
+
shouldFetch,
|
|
14
15
|
onDataLoadError,
|
|
15
16
|
});
|
|
16
17
|
const handleInputChange = (event, search, reason) => {
|
|
@@ -24,7 +25,15 @@ const useLogic = ({ fetchOptions, fetchDelayMs = constants_1.DEFAULT_DELAY_MS, m
|
|
|
24
25
|
propsOnOpen(defaultSearch, defaultValue);
|
|
25
26
|
onOpen?.(event);
|
|
26
27
|
};
|
|
27
|
-
const
|
|
28
|
+
const getEmptyOptionsText = () => {
|
|
29
|
+
if (isAllow) {
|
|
30
|
+
return noOptionsText;
|
|
31
|
+
}
|
|
32
|
+
if (hintText !== '') {
|
|
33
|
+
return hintText;
|
|
34
|
+
}
|
|
35
|
+
return constants_1.DEFAULT_EMPTY_OPTIONS_TEXT;
|
|
36
|
+
};
|
|
28
37
|
return {
|
|
29
38
|
autocompleteProps: {
|
|
30
39
|
options,
|
|
@@ -35,7 +44,7 @@ const useLogic = ({ fetchOptions, fetchDelayMs = constants_1.DEFAULT_DELAY_MS, m
|
|
|
35
44
|
required: isRequired,
|
|
36
45
|
success: isSuccess,
|
|
37
46
|
onOpen: handleOpen,
|
|
38
|
-
noOptionsText:
|
|
47
|
+
noOptionsText: getEmptyOptionsText(),
|
|
39
48
|
onInputChange: handleInputChange,
|
|
40
49
|
isLoadedDataError: isLoadedDataError ?? isLoadingError,
|
|
41
50
|
getOptionLabel,
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export declare const makeFetchOptions: (options: string[]) => import("@vitest/spy").Mock<import("@vitest/spy").Procedure>;
|
|
2
|
+
export declare const makeEmptyFetchOptions: () => import("@vitest/spy").Mock<import("@vitest/spy").Procedure>;
|
|
3
|
+
export declare const makeErrorFetchOptions: (error: Error) => import("@vitest/spy").Mock<import("@vitest/spy").Procedure>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.makeErrorFetchOptions = exports.makeEmptyFetchOptions = exports.makeFetchOptions = void 0;
|
|
4
|
+
const makeFetchOptions = (options) => vi.fn().mockResolvedValue({ options });
|
|
5
|
+
exports.makeFetchOptions = makeFetchOptions;
|
|
6
|
+
const makeEmptyFetchOptions = () => vi.fn().mockResolvedValue({ options: [] });
|
|
7
|
+
exports.makeEmptyFetchOptions = makeEmptyFetchOptions;
|
|
8
|
+
const makeErrorFetchOptions = (error) => vi.fn().mockRejectedValue(error);
|
|
9
|
+
exports.makeErrorFetchOptions = makeErrorFetchOptions;
|
|
@@ -14,4 +14,5 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./types"), exports);
|
|
17
18
|
__exportStar(require("./useAsyncOptions"), exports);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AutocompleteInputChangeReason } from '@mui/material/Autocomplete';
|
|
2
|
+
import { type ShouldFetch } from './types';
|
|
2
3
|
type FetchResult<TOptions> = {
|
|
3
4
|
options: TOptions[];
|
|
4
5
|
meta?: {
|
|
@@ -37,6 +38,10 @@ type UseAsyncOptionsParams<TOptions> = {
|
|
|
37
38
|
* Минимальное количество символов для выполнения запроса
|
|
38
39
|
*/
|
|
39
40
|
minSymbolsToFetch: number;
|
|
41
|
+
/**
|
|
42
|
+
* Функция, задающая правила валидации запроса
|
|
43
|
+
*/
|
|
44
|
+
shouldFetch?: ShouldFetch;
|
|
40
45
|
/**
|
|
41
46
|
* Задержка для debounce
|
|
42
47
|
*/
|
|
@@ -57,7 +62,7 @@ type UseAsyncOptionsParams<TOptions> = {
|
|
|
57
62
|
* Хук для асинхронной загрузки опций в компонентах с поиском.
|
|
58
63
|
* Управляет задержкой, отменой запросов и состояниями загрузки.
|
|
59
64
|
*/
|
|
60
|
-
export declare const useAsyncOptions: <TOptions>({ fetchOptions, fetchDelayMs, minSymbolsToFetch, onDataLoadError, isSmoothLoading, }: UseAsyncOptionsParams<TOptions>) => {
|
|
65
|
+
export declare const useAsyncOptions: <TOptions>({ fetchOptions, fetchDelayMs, minSymbolsToFetch, shouldFetch, onDataLoadError, isSmoothLoading, }: UseAsyncOptionsParams<TOptions>) => {
|
|
61
66
|
options: TOptions[];
|
|
62
67
|
meta: {
|
|
63
68
|
isAllDataLoaded: boolean;
|
|
@@ -65,6 +70,8 @@ export declare const useAsyncOptions: <TOptions>({ fetchOptions, fetchDelayMs, m
|
|
|
65
70
|
isLoadingData: boolean;
|
|
66
71
|
onRetry: (search: string) => void;
|
|
67
72
|
isLoadingError: boolean;
|
|
73
|
+
isAllow: boolean;
|
|
74
|
+
hintText: string;
|
|
68
75
|
debouncedFetchOptions: import("debounce").DebouncedFunction<(search: string) => void>;
|
|
69
76
|
onInputChange: (search: string, reason?: AutocompleteInputChangeReason) => void;
|
|
70
77
|
onOpen: (defaultSearch?: string, defaultValue?: TOptions | null | undefined, isAlwaysTriggerOnOpen?: boolean) => void;
|
|
@@ -8,12 +8,14 @@ const useLoadingState_1 = require("../useLoadingState");
|
|
|
8
8
|
* Хук для асинхронной загрузки опций в компонентах с поиском.
|
|
9
9
|
* Управляет задержкой, отменой запросов и состояниями загрузки.
|
|
10
10
|
*/
|
|
11
|
-
const useAsyncOptions = ({ fetchOptions, fetchDelayMs, minSymbolsToFetch, onDataLoadError, isSmoothLoading, }) => {
|
|
11
|
+
const useAsyncOptions = ({ fetchOptions, fetchDelayMs, minSymbolsToFetch, shouldFetch, onDataLoadError, isSmoothLoading, }) => {
|
|
12
12
|
const [isLoadingData, setIsLoadingData] = (0, react_1.useState)(false);
|
|
13
13
|
const [isFirstOpen, setIsFirstOpen] = (0, react_1.useState)(true);
|
|
14
14
|
const [isLoadingError, setIsLoadingError] = (0, react_1.useState)(false);
|
|
15
15
|
const abortControllerRef = (0, react_1.useRef)(null);
|
|
16
16
|
const { isShowLoader } = (0, useLoadingState_1.useLoadingState)(isLoadingData);
|
|
17
|
+
const [isAllow, setIsAllow] = (0, react_1.useState)(true);
|
|
18
|
+
const [hintText, setHintText] = (0, react_1.useState)('');
|
|
17
19
|
const [options, setOptions] = (0, react_1.useState)({
|
|
18
20
|
options: [],
|
|
19
21
|
});
|
|
@@ -42,17 +44,32 @@ const useAsyncOptions = ({ fetchOptions, fetchDelayMs, minSymbolsToFetch, onData
|
|
|
42
44
|
abortControllerRef.current = null;
|
|
43
45
|
});
|
|
44
46
|
}, fetchDelayMs), [fetchOptions, fetchDelayMs]);
|
|
45
|
-
const
|
|
47
|
+
const isAllowedSearch = (search) => {
|
|
46
48
|
if ((!search.length && minSymbolsToFetch > 0) ||
|
|
47
49
|
search.length < minSymbolsToFetch) {
|
|
50
|
+
setHintText('');
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
const shouldFetchResult = shouldFetch?.(search);
|
|
54
|
+
if (shouldFetchResult && !shouldFetchResult.isAllow) {
|
|
55
|
+
setHintText(search.length !== 0 ? shouldFetchResult.reason : '');
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
return true;
|
|
59
|
+
};
|
|
60
|
+
const onInputChange = (search, reason) => {
|
|
61
|
+
if (!isAllowedSearch(search)) {
|
|
48
62
|
if (debouncedFetchOptions.isPending) {
|
|
49
63
|
debouncedFetchOptions.clear();
|
|
50
64
|
}
|
|
51
65
|
setIsLoadingData(false);
|
|
52
66
|
setOptions({ options: [] });
|
|
67
|
+
setIsAllow(false);
|
|
53
68
|
return;
|
|
54
69
|
}
|
|
55
70
|
if (!isFirstOpen) {
|
|
71
|
+
setIsAllow(true);
|
|
72
|
+
setHintText('');
|
|
56
73
|
if (reason === 'input') {
|
|
57
74
|
setIsLoadingData(true);
|
|
58
75
|
}
|
|
@@ -62,7 +79,7 @@ const useAsyncOptions = ({ fetchOptions, fetchDelayMs, minSymbolsToFetch, onData
|
|
|
62
79
|
const onOpen = (defaultSearch = '', defaultValue, isAlwaysTriggerOnOpen = false) => {
|
|
63
80
|
if (isFirstOpen || isAlwaysTriggerOnOpen) {
|
|
64
81
|
setIsFirstOpen(false);
|
|
65
|
-
if (defaultValue ||
|
|
82
|
+
if (defaultValue || isAllowedSearch(defaultSearch)) {
|
|
66
83
|
setIsLoadingData(true);
|
|
67
84
|
debouncedFetchOptions(defaultSearch);
|
|
68
85
|
}
|
|
@@ -81,6 +98,8 @@ const useAsyncOptions = ({ fetchOptions, fetchDelayMs, minSymbolsToFetch, onData
|
|
|
81
98
|
isLoadingData: isSmoothLoading ? isShowLoader : isLoadingData,
|
|
82
99
|
onRetry,
|
|
83
100
|
isLoadingError,
|
|
101
|
+
isAllow,
|
|
102
|
+
hintText,
|
|
84
103
|
debouncedFetchOptions,
|
|
85
104
|
onInputChange,
|
|
86
105
|
onOpen,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { SecondaryAction } from '../../FormPageLayoutBase';
|
|
2
|
+
import { type FormPageLayoutSubmitButtonProps } from '../types';
|
|
2
3
|
export type FormFooterProps = {
|
|
3
4
|
/**
|
|
4
5
|
* Если true, кнопки будут недоступны для взаимодействия
|
|
@@ -13,5 +14,9 @@ export type FormFooterProps = {
|
|
|
13
14
|
* @default 'Отправить'
|
|
14
15
|
*/
|
|
15
16
|
submitButtonText?: string;
|
|
17
|
+
/**
|
|
18
|
+
* Параметры для кнопки отправки формы
|
|
19
|
+
*/
|
|
20
|
+
submitButtonProps?: FormPageLayoutSubmitButtonProps;
|
|
16
21
|
};
|
|
17
22
|
export declare const FormPageFooter: (props: FormFooterProps) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -7,10 +7,10 @@ const hooks_1 = require("./hooks");
|
|
|
7
7
|
const SecondaryActions_1 = require("./SecondaryActions");
|
|
8
8
|
const styles_1 = require("./styles");
|
|
9
9
|
const FormPageFooter = (props) => {
|
|
10
|
-
const { isDisabled, submitButtonText = 'Отправить', secondaryActions, } = props;
|
|
10
|
+
const { isDisabled, submitButtonText = 'Отправить', secondaryActions, submitButtonProps, } = props;
|
|
11
11
|
const { isActive, handleToggle, isShowMoreButton } = (0, hooks_1.useFormFooter)({
|
|
12
12
|
secondaryActions,
|
|
13
13
|
});
|
|
14
|
-
return ((0, jsx_runtime_1.jsxs)(FormPageFooterContainer_1.FormPageFooterContainer, { children: [(0, jsx_runtime_1.jsxs)(styles_1.ActionsWrapper, { "$isShowMoreButton": isShowMoreButton, children: [(0, jsx_runtime_1.jsx)(styles_1.StyledFormSubmitButton, { size: "large", disabled: isDisabled, children: submitButtonText }), secondaryActions && ((0, jsx_runtime_1.jsx)(SecondaryActions_1.SecondaryActions, { isDisabled: isDisabled, actions: secondaryActions, isActive: isActive }))] }), isShowMoreButton && ((0, jsx_runtime_1.jsxs)(styles_1.ShowMoreButton, { disabled: isDisabled, variant: "text", onClick: handleToggle, "$isActive": isActive, children: [isActive ? 'Скрыть' : 'Показать', " \u0434\u0440\u0443\u0433\u0438\u0435 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044F"] }))] }));
|
|
14
|
+
return ((0, jsx_runtime_1.jsxs)(FormPageFooterContainer_1.FormPageFooterContainer, { children: [(0, jsx_runtime_1.jsxs)(styles_1.ActionsWrapper, { "$isShowMoreButton": isShowMoreButton, children: [(0, jsx_runtime_1.jsx)(styles_1.StyledFormSubmitButton, { size: "large", disabled: isDisabled, ...submitButtonProps, children: submitButtonText }), secondaryActions && ((0, jsx_runtime_1.jsx)(SecondaryActions_1.SecondaryActions, { isDisabled: isDisabled, actions: secondaryActions, isActive: isActive }))] }), isShowMoreButton && ((0, jsx_runtime_1.jsxs)(styles_1.ShowMoreButton, { disabled: isDisabled, variant: "text", onClick: handleToggle, "$isActive": isActive, children: [isActive ? 'Скрыть' : 'Показать', " \u0434\u0440\u0443\u0433\u0438\u0435 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044F"] }))] }));
|
|
15
15
|
};
|
|
16
16
|
exports.FormPageFooter = FormPageFooter;
|
|
@@ -1,29 +1,5 @@
|
|
|
1
|
-
import { type
|
|
2
|
-
import { type PageLayoutProps } from '../../components/PageLayout';
|
|
3
|
-
import { type FormProps } from '../Form';
|
|
4
|
-
import type { SecondaryAction } from '../FormPageLayoutBase';
|
|
5
|
-
type FormPageProps<TFormValues extends Record<string, unknown>> = Omit<FormProps<TFormValues>, 'children'> & {
|
|
6
|
-
/**
|
|
7
|
-
* Текст для кнопки отправки формы
|
|
8
|
-
*/
|
|
9
|
-
submitButtonText?: string;
|
|
10
|
-
/**
|
|
11
|
-
* Дополнительные кнопки формы
|
|
12
|
-
*/
|
|
13
|
-
secondaryActions?: SecondaryAction[];
|
|
14
|
-
};
|
|
15
|
-
export type FormPageLayoutProps<TFormValues extends Record<string, unknown>> = Omit<PageLayoutProps, 'content'> & {
|
|
16
|
-
/**
|
|
17
|
-
* Содержимое формы
|
|
18
|
-
*/
|
|
19
|
-
children: ReactNode;
|
|
20
|
-
/**
|
|
21
|
-
* Параметры страницы формы
|
|
22
|
-
*/
|
|
23
|
-
form: FormPageProps<TFormValues>;
|
|
24
|
-
};
|
|
1
|
+
import { type FormPageLayoutProps } from './types';
|
|
25
2
|
/**
|
|
26
3
|
* FormPageLayout предназначен для построения страниц с формами.
|
|
27
4
|
*/
|
|
28
5
|
export declare const FormPageLayout: <TFormValues extends Record<string, unknown>>(props: FormPageLayoutProps<TFormValues>) => import("react/jsx-runtime").JSX.Element;
|
|
29
|
-
export {};
|
|
@@ -12,9 +12,9 @@ const styles_1 = require("./styles");
|
|
|
12
12
|
*/
|
|
13
13
|
const FormPageLayout = (props) => {
|
|
14
14
|
const { form, children, ...restProps } = props;
|
|
15
|
-
const { secondaryActions, submitButtonText, ...restFormProps } = form;
|
|
15
|
+
const { secondaryActions, submitButtonText, submitButtonProps, ...restFormProps } = form;
|
|
16
16
|
return ((0, jsx_runtime_1.jsx)(FormPageLayoutBase_1.FormPageLayoutBase, { ...restProps, formProps: { ...form, secondaryActions }, contentClassname: (0, cn_1.cn)({
|
|
17
17
|
[FormPageLayoutBase_1.formPageContentClassnames.formPageContentSecondaryActions]: Boolean(secondaryActions),
|
|
18
|
-
}), children: ({ isDisabled }) => ((0, jsx_runtime_1.jsxs)(Form_1.Form, { ...restFormProps, isDisabled: isDisabled, children: [(0, jsx_runtime_1.jsx)(styles_1.FormContent, { children: children }), (0, jsx_runtime_1.jsx)(FormPageFooter_1.FormPageFooter, { isDisabled: isDisabled, submitButtonText: submitButtonText, secondaryActions: secondaryActions })] })) }));
|
|
18
|
+
}), children: ({ isDisabled }) => ((0, jsx_runtime_1.jsxs)(Form_1.Form, { ...restFormProps, isDisabled: isDisabled, children: [(0, jsx_runtime_1.jsx)(styles_1.FormContent, { children: children }), (0, jsx_runtime_1.jsx)(FormPageFooter_1.FormPageFooter, { isDisabled: isDisabled, submitButtonText: submitButtonText, secondaryActions: secondaryActions, submitButtonProps: submitButtonProps })] })) }));
|
|
19
19
|
};
|
|
20
20
|
exports.FormPageLayout = FormPageLayout;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import { type PageLayoutProps } from '../../components/PageLayout';
|
|
3
|
+
import { type FormProps } from '../Form';
|
|
4
|
+
import { type SecondaryAction } from '../FormPageLayoutBase';
|
|
5
|
+
import { type FormSubmitButtonProps } from '../FormSubmitButton';
|
|
6
|
+
export type FormPageLayoutSubmitButtonProps = Pick<FormSubmitButtonProps, 'color' | 'startIcon' | 'endIcon'>;
|
|
7
|
+
type FormPageProps<TFormValues extends Record<string, unknown>> = Omit<FormProps<TFormValues>, 'children'> & {
|
|
8
|
+
/**
|
|
9
|
+
* Параметры для кнопки отправки формы
|
|
10
|
+
*/
|
|
11
|
+
submitButtonProps?: FormPageLayoutSubmitButtonProps;
|
|
12
|
+
/**
|
|
13
|
+
* Текст для кнопки отправки формы
|
|
14
|
+
*/
|
|
15
|
+
submitButtonText?: string;
|
|
16
|
+
/**
|
|
17
|
+
* Дополнительные кнопки формы
|
|
18
|
+
*/
|
|
19
|
+
secondaryActions?: SecondaryAction[];
|
|
20
|
+
};
|
|
21
|
+
export type FormPageLayoutProps<TFormValues extends Record<string, unknown>> = Omit<PageLayoutProps, 'content'> & {
|
|
22
|
+
/**
|
|
23
|
+
* Содержимое формы
|
|
24
|
+
*/
|
|
25
|
+
children: ReactNode;
|
|
26
|
+
/**
|
|
27
|
+
* Параметры страницы формы
|
|
28
|
+
*/
|
|
29
|
+
form: FormPageProps<TFormValues>;
|
|
30
|
+
};
|
|
31
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { faker } from '@ui/dev/faker';
|
|
2
|
-
import axios from 'axios';
|
|
3
|
-
import AxiosMockAdapter from 'axios-mock-adapter';
|
|
4
|
-
const getRandomDelay = () => Math.random() * 100 + 100; // 200-500ms
|
|
5
|
-
const axiosInstance = axios.create();
|
|
6
|
-
const mock = new AxiosMockAdapter(axiosInstance, {
|
|
7
|
-
delayResponse: getRandomDelay(),
|
|
8
|
-
});
|
|
9
|
-
const generateFakeUsers = (count) => {
|
|
10
|
-
return Array.from({ length: count - 1 }).map(() => ({
|
|
11
|
-
id: faker.string.uuid(),
|
|
12
|
-
name: faker.person.fullName(),
|
|
13
|
-
}));
|
|
14
|
-
};
|
|
15
|
-
const FAKE_USERS = [
|
|
16
|
-
{
|
|
17
|
-
id: '1',
|
|
18
|
-
name: 'Иван Третьяков',
|
|
19
|
-
},
|
|
20
|
-
...generateFakeUsers(30),
|
|
21
|
-
];
|
|
22
|
-
mock.onGet('/api/users').reply((config) => {
|
|
23
|
-
const search = new URLSearchParams(config.params).get('name_like') || '';
|
|
24
|
-
const users = FAKE_USERS.filter((user) => user.name.toLowerCase().includes(search.toLowerCase()));
|
|
25
|
-
return [200, users];
|
|
26
|
-
});
|
|
27
|
-
mock.onGet('/api/users/id').reply((config) => {
|
|
28
|
-
const userId = new URLSearchParams(config.params).get('id') || '';
|
|
29
|
-
const user = FAKE_USERS.find((fakeUser) => fakeUser.id === userId);
|
|
30
|
-
return [200, user];
|
|
31
|
-
});
|
|
32
|
-
export const getFakeUsers = async (search, options) => {
|
|
33
|
-
const response = await axiosInstance.get('api/users', {
|
|
34
|
-
...options,
|
|
35
|
-
params: { ...options?.params, name_like: search },
|
|
36
|
-
});
|
|
37
|
-
return response.data;
|
|
38
|
-
};
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.getFakeUsers = void 0;
|
|
7
|
-
const faker_1 = require("@ui/dev/faker");
|
|
8
|
-
const axios_1 = __importDefault(require("axios"));
|
|
9
|
-
const axios_mock_adapter_1 = __importDefault(require("axios-mock-adapter"));
|
|
10
|
-
const getRandomDelay = () => Math.random() * 100 + 100; // 200-500ms
|
|
11
|
-
const axiosInstance = axios_1.default.create();
|
|
12
|
-
const mock = new axios_mock_adapter_1.default(axiosInstance, {
|
|
13
|
-
delayResponse: getRandomDelay(),
|
|
14
|
-
});
|
|
15
|
-
const generateFakeUsers = (count) => {
|
|
16
|
-
return Array.from({ length: count - 1 }).map(() => ({
|
|
17
|
-
id: faker_1.faker.string.uuid(),
|
|
18
|
-
name: faker_1.faker.person.fullName(),
|
|
19
|
-
}));
|
|
20
|
-
};
|
|
21
|
-
const FAKE_USERS = [
|
|
22
|
-
{
|
|
23
|
-
id: '1',
|
|
24
|
-
name: 'Иван Третьяков',
|
|
25
|
-
},
|
|
26
|
-
...generateFakeUsers(30),
|
|
27
|
-
];
|
|
28
|
-
mock.onGet('/api/users').reply((config) => {
|
|
29
|
-
const search = new URLSearchParams(config.params).get('name_like') || '';
|
|
30
|
-
const users = FAKE_USERS.filter((user) => user.name.toLowerCase().includes(search.toLowerCase()));
|
|
31
|
-
return [200, users];
|
|
32
|
-
});
|
|
33
|
-
mock.onGet('/api/users/id').reply((config) => {
|
|
34
|
-
const userId = new URLSearchParams(config.params).get('id') || '';
|
|
35
|
-
const user = FAKE_USERS.find((fakeUser) => fakeUser.id === userId);
|
|
36
|
-
return [200, user];
|
|
37
|
-
});
|
|
38
|
-
const getFakeUsers = async (search, options) => {
|
|
39
|
-
const response = await axiosInstance.get('api/users', {
|
|
40
|
-
...options,
|
|
41
|
-
params: { ...options?.params, name_like: search },
|
|
42
|
-
});
|
|
43
|
-
return response.data;
|
|
44
|
-
};
|
|
45
|
-
exports.getFakeUsers = getFakeUsers;
|