@astral/ui 4.25.1 → 4.26.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.
Files changed (165) hide show
  1. package/components/BottomDrawer/BottomDrawer.d.ts +6 -2
  2. package/components/BottomDrawer/BottomDrawer.js +2 -2
  3. package/components/DashboardSidebarPopover/styles.d.ts +1 -0
  4. package/components/Dialog/Dialog.d.ts +6 -2
  5. package/components/Dialog/Dialog.js +3 -3
  6. package/components/DialogTitle/DialogTitle.d.ts +6 -1
  7. package/components/DialogTitle/DialogTitle.js +2 -2
  8. package/components/PageHeader/HeaderContent/HeaderContent.d.ts +2 -2
  9. package/components/PageHeader/HeaderContent/HeaderContent.js +3 -3
  10. package/components/PageHeader/HeaderContent/styles.d.ts +4 -0
  11. package/components/PageHeader/HeaderContent/styles.js +4 -0
  12. package/components/PageHeader/PageHeader.js +2 -2
  13. package/components/PageHeader/types.d.ts +6 -2
  14. package/components/RadioGroup/types.d.ts +0 -3
  15. package/components/SideDialogTitle/styles.d.ts +2 -0
  16. package/components/Tree/types.d.ts +0 -3
  17. package/components/TreeLikeAutocomplete/types.d.ts +0 -3
  18. package/hook-form/FormFilters/FormFilters.d.ts +5 -0
  19. package/hook-form/FormFilters/FormFilters.js +20 -0
  20. package/hook-form/FormFilters/FormFiltersDialog/FormFiltersDialog.d.ts +5 -0
  21. package/hook-form/FormFilters/FormFiltersDialog/FormFiltersDialog.js +15 -0
  22. package/hook-form/FormFilters/FormFiltersDialog/index.d.ts +1 -0
  23. package/hook-form/FormFilters/FormFiltersDialog/index.js +1 -0
  24. package/hook-form/FormFilters/FormFiltersDialog/styles.d.ts +29 -0
  25. package/hook-form/FormFilters/FormFiltersDialog/styles.js +33 -0
  26. package/hook-form/FormFilters/FormFiltersDialog/types.d.ts +20 -0
  27. package/hook-form/FormFilters/FormFiltersDialog/types.js +1 -0
  28. package/hook-form/FormFilters/FormFiltersDialog/useLogic/index.d.ts +1 -0
  29. package/hook-form/FormFilters/FormFiltersDialog/useLogic/index.js +1 -0
  30. package/hook-form/FormFilters/FormFiltersDialog/useLogic/useLogic.d.ts +18 -0
  31. package/hook-form/FormFilters/FormFiltersDialog/useLogic/useLogic.js +38 -0
  32. package/hook-form/FormFilters/FormFiltersSkeleton/FormFiltersSkeleton.d.ts +1 -0
  33. package/hook-form/FormFilters/FormFiltersSkeleton/FormFiltersSkeleton.js +6 -0
  34. package/hook-form/FormFilters/FormFiltersSkeleton/index.d.ts +1 -0
  35. package/hook-form/FormFilters/FormFiltersSkeleton/index.js +1 -0
  36. package/hook-form/FormFilters/FormFiltersSkeleton/styles.d.ts +5 -0
  37. package/hook-form/FormFilters/FormFiltersSkeleton/styles.js +24 -0
  38. package/hook-form/FormFilters/constants.d.ts +10 -0
  39. package/hook-form/FormFilters/constants.js +11 -0
  40. package/hook-form/FormFilters/index.d.ts +3 -0
  41. package/hook-form/FormFilters/index.js +2 -0
  42. package/hook-form/FormFilters/public.d.ts +3 -0
  43. package/hook-form/FormFilters/public.js +2 -0
  44. package/hook-form/FormFilters/styles.d.ts +29 -0
  45. package/hook-form/FormFilters/styles.js +94 -0
  46. package/hook-form/FormFilters/types.d.ts +92 -0
  47. package/hook-form/FormFilters/types.js +1 -0
  48. package/hook-form/FormFilters/useLogic/hooks/index.d.ts +1 -0
  49. package/hook-form/FormFilters/useLogic/hooks/index.js +1 -0
  50. package/hook-form/FormFilters/useLogic/hooks/useFirstMountAfterLoading.d.ts +1 -0
  51. package/hook-form/FormFilters/useLogic/hooks/useFirstMountAfterLoading.js +9 -0
  52. package/hook-form/FormFilters/useLogic/index.d.ts +1 -0
  53. package/hook-form/FormFilters/useLogic/index.js +1 -0
  54. package/hook-form/FormFilters/useLogic/useLogic.d.ts +18 -0
  55. package/hook-form/FormFilters/useLogic/useLogic.js +120 -0
  56. package/hook-form/FormFiltersModalGroup/FormFiltersModalGroup.d.ts +10 -0
  57. package/hook-form/FormFiltersModalGroup/FormFiltersModalGroup.js +13 -0
  58. package/hook-form/FormFiltersModalGroup/index.d.ts +2 -0
  59. package/hook-form/FormFiltersModalGroup/index.js +1 -0
  60. package/hook-form/FormFiltersModalGroup/public.d.ts +2 -0
  61. package/hook-form/FormFiltersModalGroup/public.js +1 -0
  62. package/hook-form/FormFiltersModalGroup/styles.d.ts +4 -0
  63. package/hook-form/FormFiltersModalGroup/styles.js +23 -0
  64. package/hook-form/FormFiltersModalGroup/types.d.ts +9 -0
  65. package/hook-form/FormFiltersModalGroup/types.js +1 -0
  66. package/hook-form/FormFiltersSearchField/FormFiltersSearchField.d.ts +9 -0
  67. package/hook-form/FormFiltersSearchField/FormFiltersSearchField.js +17 -0
  68. package/hook-form/FormFiltersSearchField/constants.d.ts +1 -0
  69. package/hook-form/FormFiltersSearchField/constants.js +1 -0
  70. package/hook-form/FormFiltersSearchField/index.d.ts +1 -0
  71. package/hook-form/FormFiltersSearchField/index.js +1 -0
  72. package/hook-form/FormFiltersSearchField/public.d.ts +1 -0
  73. package/hook-form/FormFiltersSearchField/public.js +1 -0
  74. package/hook-form/FormFiltersSearchField/styles.d.ts +7 -0
  75. package/hook-form/FormFiltersSearchField/styles.js +8 -0
  76. package/hook-form/FormPageLayoutBase/useLogic/useLogic.d.ts +1 -0
  77. package/hook-form/FormSearchField/FormSearchField.d.ts +5 -0
  78. package/hook-form/FormSearchField/FormSearchField.js +15 -0
  79. package/hook-form/FormSearchField/index.d.ts +1 -0
  80. package/hook-form/FormSearchField/index.js +1 -0
  81. package/hook-form/FormSearchField/public.d.ts +1 -0
  82. package/hook-form/FormSearchField/public.js +1 -0
  83. package/node/components/BottomDrawer/BottomDrawer.d.ts +6 -2
  84. package/node/components/BottomDrawer/BottomDrawer.js +2 -2
  85. package/node/components/DashboardSidebarPopover/styles.d.ts +1 -0
  86. package/node/components/Dialog/Dialog.d.ts +6 -2
  87. package/node/components/Dialog/Dialog.js +3 -3
  88. package/node/components/DialogTitle/DialogTitle.d.ts +6 -1
  89. package/node/components/DialogTitle/DialogTitle.js +2 -2
  90. package/node/components/PageHeader/HeaderContent/HeaderContent.d.ts +2 -2
  91. package/node/components/PageHeader/HeaderContent/HeaderContent.js +2 -2
  92. package/node/components/PageHeader/HeaderContent/styles.d.ts +4 -0
  93. package/node/components/PageHeader/HeaderContent/styles.js +5 -1
  94. package/node/components/PageHeader/PageHeader.js +2 -2
  95. package/node/components/PageHeader/types.d.ts +6 -2
  96. package/node/components/RadioGroup/types.d.ts +0 -3
  97. package/node/components/SideDialogTitle/styles.d.ts +2 -0
  98. package/node/components/Tree/types.d.ts +0 -3
  99. package/node/components/TreeLikeAutocomplete/types.d.ts +0 -3
  100. package/node/hook-form/FormFilters/FormFilters.d.ts +5 -0
  101. package/node/hook-form/FormFilters/FormFilters.js +24 -0
  102. package/node/hook-form/FormFilters/FormFiltersDialog/FormFiltersDialog.d.ts +5 -0
  103. package/node/hook-form/FormFilters/FormFiltersDialog/FormFiltersDialog.js +19 -0
  104. package/node/hook-form/FormFilters/FormFiltersDialog/index.d.ts +1 -0
  105. package/node/hook-form/FormFilters/FormFiltersDialog/index.js +5 -0
  106. package/node/hook-form/FormFilters/FormFiltersDialog/styles.d.ts +29 -0
  107. package/node/hook-form/FormFilters/FormFiltersDialog/styles.js +36 -0
  108. package/node/hook-form/FormFilters/FormFiltersDialog/types.d.ts +20 -0
  109. package/node/hook-form/FormFilters/FormFiltersDialog/types.js +2 -0
  110. package/node/hook-form/FormFilters/FormFiltersDialog/useLogic/index.d.ts +1 -0
  111. package/node/hook-form/FormFilters/FormFiltersDialog/useLogic/index.js +17 -0
  112. package/node/hook-form/FormFilters/FormFiltersDialog/useLogic/useLogic.d.ts +18 -0
  113. package/node/hook-form/FormFilters/FormFiltersDialog/useLogic/useLogic.js +42 -0
  114. package/node/hook-form/FormFilters/FormFiltersSkeleton/FormFiltersSkeleton.d.ts +1 -0
  115. package/node/hook-form/FormFilters/FormFiltersSkeleton/FormFiltersSkeleton.js +10 -0
  116. package/node/hook-form/FormFilters/FormFiltersSkeleton/index.d.ts +1 -0
  117. package/node/hook-form/FormFilters/FormFiltersSkeleton/index.js +5 -0
  118. package/node/hook-form/FormFilters/FormFiltersSkeleton/styles.d.ts +5 -0
  119. package/node/hook-form/FormFilters/FormFiltersSkeleton/styles.js +27 -0
  120. package/node/hook-form/FormFilters/constants.d.ts +10 -0
  121. package/node/hook-form/FormFilters/constants.js +14 -0
  122. package/node/hook-form/FormFilters/index.d.ts +3 -0
  123. package/node/hook-form/FormFilters/index.js +7 -0
  124. package/node/hook-form/FormFilters/public.d.ts +3 -0
  125. package/node/hook-form/FormFilters/public.js +7 -0
  126. package/node/hook-form/FormFilters/styles.d.ts +29 -0
  127. package/node/hook-form/FormFilters/styles.js +97 -0
  128. package/node/hook-form/FormFilters/types.d.ts +92 -0
  129. package/node/hook-form/FormFilters/types.js +2 -0
  130. package/node/hook-form/FormFilters/useLogic/hooks/index.d.ts +1 -0
  131. package/node/hook-form/FormFilters/useLogic/hooks/index.js +5 -0
  132. package/node/hook-form/FormFilters/useLogic/hooks/useFirstMountAfterLoading.d.ts +1 -0
  133. package/node/hook-form/FormFilters/useLogic/hooks/useFirstMountAfterLoading.js +13 -0
  134. package/node/hook-form/FormFilters/useLogic/index.d.ts +1 -0
  135. package/node/hook-form/FormFilters/useLogic/index.js +17 -0
  136. package/node/hook-form/FormFilters/useLogic/useLogic.d.ts +18 -0
  137. package/node/hook-form/FormFilters/useLogic/useLogic.js +124 -0
  138. package/node/hook-form/FormFiltersModalGroup/FormFiltersModalGroup.d.ts +10 -0
  139. package/node/hook-form/FormFiltersModalGroup/FormFiltersModalGroup.js +17 -0
  140. package/node/hook-form/FormFiltersModalGroup/index.d.ts +2 -0
  141. package/node/hook-form/FormFiltersModalGroup/index.js +5 -0
  142. package/node/hook-form/FormFiltersModalGroup/public.d.ts +2 -0
  143. package/node/hook-form/FormFiltersModalGroup/public.js +5 -0
  144. package/node/hook-form/FormFiltersModalGroup/styles.d.ts +4 -0
  145. package/node/hook-form/FormFiltersModalGroup/styles.js +26 -0
  146. package/node/hook-form/FormFiltersModalGroup/types.d.ts +9 -0
  147. package/node/hook-form/FormFiltersModalGroup/types.js +2 -0
  148. package/node/hook-form/FormFiltersSearchField/FormFiltersSearchField.d.ts +9 -0
  149. package/node/hook-form/FormFiltersSearchField/FormFiltersSearchField.js +20 -0
  150. package/node/hook-form/FormFiltersSearchField/constants.d.ts +1 -0
  151. package/node/hook-form/FormFiltersSearchField/constants.js +4 -0
  152. package/node/hook-form/FormFiltersSearchField/index.d.ts +1 -0
  153. package/node/hook-form/FormFiltersSearchField/index.js +5 -0
  154. package/node/hook-form/FormFiltersSearchField/public.d.ts +1 -0
  155. package/node/hook-form/FormFiltersSearchField/public.js +5 -0
  156. package/node/hook-form/FormFiltersSearchField/styles.d.ts +7 -0
  157. package/node/hook-form/FormFiltersSearchField/styles.js +11 -0
  158. package/node/hook-form/FormPageLayoutBase/useLogic/useLogic.d.ts +1 -0
  159. package/node/hook-form/FormSearchField/FormSearchField.d.ts +5 -0
  160. package/node/hook-form/FormSearchField/FormSearchField.js +18 -0
  161. package/node/hook-form/FormSearchField/index.d.ts +1 -0
  162. package/node/hook-form/FormSearchField/index.js +5 -0
  163. package/node/hook-form/FormSearchField/public.d.ts +1 -0
  164. package/node/hook-form/FormSearchField/public.js +5 -0
  165. package/package.json +1 -1
@@ -0,0 +1,94 @@
1
+ import { badgeClasses } from '@mui/material/Badge';
2
+ import { formLabelClasses } from '@mui/material/FormLabel';
3
+ import { inputBaseClasses } from '@mui/material/InputBase';
4
+ import { Badge } from '../../components/Badge';
5
+ import { Counter } from '../../components/Counter';
6
+ import { styled } from '../../components/styled';
7
+ import { formFiltersClassnames } from './constants';
8
+ export const Wrapper = styled.div `
9
+ display: flex;
10
+ gap: ${({ theme }) => theme.spacing(2)};
11
+ justify-content: space-between;
12
+
13
+ width: 100%;
14
+ margin-top: ${({ theme }) => theme.spacing(4)};
15
+
16
+ .${formLabelClasses.root} {
17
+ display: none;
18
+ }
19
+
20
+ ${({ theme }) => theme.breakpoints.down('sm')} {
21
+ margin-top: 0;
22
+ padding: ${({ theme }) => theme.spacing(1, 4, 5, 4)};
23
+
24
+ font-size: 0;
25
+
26
+ .${inputBaseClasses.input} {
27
+ padding: ${({ theme }) => theme.spacing(2, 3)};
28
+ }
29
+
30
+ .${formFiltersClassnames.submitButton} {
31
+ display: none;
32
+ }
33
+ }
34
+ `;
35
+ export const AllFiltersButtonWrapper = styled.div `
36
+ display: none;
37
+
38
+ white-space: nowrap;
39
+
40
+ &.${formFiltersClassnames.showAllFiltersButtonOnDesktop} {
41
+ display: flex;
42
+ }
43
+
44
+ ${({ theme }) => theme.breakpoints.down('sm')} {
45
+ margin-right: 0;
46
+ &.${formFiltersClassnames.showAllFiltersButtonOnMobile} {
47
+ display: flex;
48
+ }
49
+
50
+ &.${formFiltersClassnames.withoutSearch} {
51
+ width: 100%;
52
+
53
+ .${formFiltersClassnames.allFiltersButton} {
54
+ width: 100%;
55
+ }
56
+ }
57
+
58
+ &.${formFiltersClassnames.withSearch} {
59
+ .${formFiltersClassnames.allFiltersButton} {
60
+ display: unset;
61
+ gap: 0;
62
+
63
+ padding: 8px;
64
+
65
+ font-size: 0;
66
+ }
67
+ }
68
+ }
69
+ `;
70
+ export const MainFiltersWrapper = styled.div `
71
+ display: grid;
72
+ grid-auto-flow: column;
73
+ grid-template-columns: ${({ columns }) => columns || 'repeat(auto-fit, min(200px, 1fr))'};
74
+ column-gap: ${({ theme }) => theme.spacing(2)};
75
+
76
+ width: 100%;
77
+
78
+ ${({ theme }) => theme.breakpoints.down('sm')} {
79
+ display: none;
80
+ }
81
+ `;
82
+ export const StyledBadge = styled(Badge) `
83
+ width: 100%;
84
+ .${badgeClasses.dot} {
85
+ ${({ theme }) => theme.breakpoints.up('sm')} {
86
+ display: none;
87
+ }
88
+ }
89
+ `;
90
+ export const StyledCounter = styled(Counter) `
91
+ ${({ theme }) => theme.breakpoints.down('sm')} {
92
+ display: none;
93
+ }
94
+ `;
@@ -0,0 +1,92 @@
1
+ import { type ObjectAsyncGuard, type ObjectGuard } from '@astral/validations';
2
+ import { type ReactNode } from 'react';
3
+ import { type Control } from 'react-hook-form';
4
+ import type { ButtonProps } from '../../components/Button';
5
+ type InputsProps<TFieldValues extends Record<string, unknown>> = {
6
+ control: Control<TFieldValues>;
7
+ };
8
+ export type FormFiltersProps<TFieldValues extends Record<string, unknown>> = {
9
+ /**
10
+ * Схема валидации из @astral/validations
11
+ */
12
+ validationSchema?: ObjectGuard<TFieldValues> | ObjectAsyncGuard<TFieldValues>;
13
+ /**
14
+ * Значения фильтров
15
+ */
16
+ values?: TFieldValues;
17
+ /**
18
+ * Callback вызываемый при изменении значений фильтров. Вызывается с debounce (300ms)
19
+ */
20
+ onChange: (values: TFieldValues) => void;
21
+ /**
22
+ * Поле поиска
23
+ *
24
+ * @example
25
+ * ```ts
26
+ * <FormFilters
27
+ * SearchField={(props) => <FormFiltersSearchField {...props} placeholder="Поиск..." />}
28
+ * />
29
+ * ```
30
+ */
31
+ searchField?: (props: InputsProps<TFieldValues> & {
32
+ fullWidth?: boolean;
33
+ }) => ReactNode;
34
+ /**
35
+ * Основные фильтры
36
+ *
37
+ * @example
38
+ * ```ts
39
+ * <FormFilters
40
+ * MainFilters={({ control, isModal, ...props }) =>
41
+ * <FormTextField
42
+ * {...props}
43
+ * control={control}
44
+ * name="name"
45
+ * label="Название"
46
+ * placeholder={isModal ? 'Введите название' : 'Название'}
47
+ * />
48
+ * />
49
+ * ```
50
+ */
51
+ mainFilters?: (props: InputsProps<TFieldValues> & {
52
+ isModal?: boolean;
53
+ }) => ReactNode;
54
+ /**
55
+ * Фильтры, отображаемые в модальном окне "Все фильтры"
56
+ *
57
+ * @example
58
+ * ```ts
59
+ * <FormFilters
60
+ * SecondaryFilters={({ control, ...props }) => <FormTextField {...props} control={control} placeholder="Введите название" />}
61
+ * />
62
+ * ```
63
+ */
64
+ secondaryFilters?: (props: InputsProps<TFieldValues>) => ReactNode;
65
+ /**
66
+ * Кнопка отправки формы
67
+ *
68
+ * @example
69
+ * ```ts
70
+ * <FormFilters
71
+ * SubmitButton={(props) => <Button {...props} loading={isLoading}>Применить</Button>}
72
+ * />
73
+ * ```
74
+ */
75
+ submitButton?: (props: ButtonProps) => ReactNode;
76
+ /**
77
+ * Определение ширины колонок для основных фильтров.
78
+ * Поддерживает CSS grid-template-columns синтаксис:
79
+ * - minmax(min, max): например, "minmax(150px, 200px) minmax(200px, 300px)"
80
+ * - fr (fractional units): например, "1fr 2fr"
81
+ * - px (pixels): например, "200px 300px"
82
+ * - Комбинации: например, "minmax(150px, 1fr) 2fr"
83
+ *
84
+ * @default "1fr"
85
+ */
86
+ mainFiltersColumns?: string;
87
+ /**
88
+ * Флаг загрузки фильтров, используйте когда фильтры загружаются динамически с сервера
89
+ */
90
+ isLoading?: boolean;
91
+ };
92
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export { useFirstMountAfterLoading } from './useFirstMountAfterLoading';
@@ -0,0 +1 @@
1
+ export { useFirstMountAfterLoading } from './useFirstMountAfterLoading';
@@ -0,0 +1 @@
1
+ export declare function useFirstMountAfterLoading(isLoading?: boolean): boolean;
@@ -0,0 +1,9 @@
1
+ import { useRef } from 'react';
2
+ export function useFirstMountAfterLoading(isLoading) {
3
+ const isFirst = useRef(true);
4
+ if (isFirst.current && !isLoading) {
5
+ isFirst.current = false;
6
+ return true;
7
+ }
8
+ return isFirst.current;
9
+ }
@@ -0,0 +1 @@
1
+ export * from './useLogic';
@@ -0,0 +1 @@
1
+ export * from './useLogic';
@@ -0,0 +1,18 @@
1
+ import { type UseFormReturn } from '../../useForm';
2
+ import { type FormFiltersDialogProps } from '../FormFiltersDialog/types';
3
+ import { type FormFiltersProps } from '../types';
4
+ type UseLogicReturn<TFieldValues extends Record<string, unknown>> = {
5
+ headerForm: UseFormReturn<TFieldValues>;
6
+ searchField: FormFiltersProps<TFieldValues>['searchField'];
7
+ mainFilters: FormFiltersProps<TFieldValues>['mainFilters'];
8
+ submitButton: FormFiltersProps<TFieldValues>['submitButton'];
9
+ counter: number;
10
+ onSubmit: (submittedValues: TFieldValues) => void;
11
+ onOpenModal: () => void;
12
+ dialogProps: FormFiltersDialogProps<TFieldValues>;
13
+ mainFiltersColumns?: string;
14
+ isLoading?: boolean;
15
+ allFiltersButtonClassName: string;
16
+ };
17
+ export declare const useLogic: <TFieldValues extends Record<string, unknown>>({ validationSchema, values, onChange, searchField, mainFilters, secondaryFilters, submitButton, mainFiltersColumns, isLoading, }: FormFiltersProps<TFieldValues>) => UseLogicReturn<TFieldValues>;
18
+ export {};
@@ -0,0 +1,120 @@
1
+ import { debounce } from '@astral/utils/debounce';
2
+ import { useEffect, useMemo, useRef, useState } from 'react';
3
+ import { useWatch } from 'react-hook-form';
4
+ import { checkIsDeepEqual } from '../../../components/utils/checkIsDeepEqual';
5
+ import { classNames } from '../../../components/utils/classNames';
6
+ import { FORM_FILTERS_SEARCH_FIELD_ID } from '../../FormFiltersSearchField/constants';
7
+ import { useForm } from '../../useForm';
8
+ import { formFiltersClassnames } from '../constants';
9
+ import { useFirstMountAfterLoading } from './hooks';
10
+ export const useLogic = ({ validationSchema, values, onChange, searchField, mainFilters, secondaryFilters, submitButton, mainFiltersColumns, isLoading, }) => {
11
+ const [isModalOpen, setIsModalOpen] = useState(false);
12
+ const isFirstMount = useFirstMountAfterLoading(isLoading);
13
+ const onOpenModal = () => {
14
+ setIsModalOpen(true);
15
+ };
16
+ const headerForm = useForm({
17
+ validationSchema,
18
+ defaultValues: values,
19
+ });
20
+ const headerFormValues = useWatch({
21
+ control: headerForm.control,
22
+ });
23
+ // Ref для хранения debounced функции, чтобы можно было её отменить
24
+ const debouncedOnChangeRef = useRef(null);
25
+ // Ref для отслеживания внешнего reset (чтобы не вызывать onChange при внешнем reset)
26
+ const isExternalResetRef = useRef(false);
27
+ // Ref для хранения предыдущих внешних values (для сравнения)
28
+ const previousValuesRef = useRef(values);
29
+ // Отменяет предыдущий debounced вызов onChange, если он есть
30
+ const clearPreviousDebouncedOnChange = () => {
31
+ if (debouncedOnChangeRef.current) {
32
+ debouncedOnChangeRef.current.clear();
33
+ }
34
+ };
35
+ const hasSubmitButton = Boolean(submitButton);
36
+ useEffect(() => {
37
+ // Если есть submitButton, не вызываем onChange при изменении значений
38
+ if (hasSubmitButton || isLoading || isFirstMount) {
39
+ return;
40
+ }
41
+ clearPreviousDebouncedOnChange();
42
+ // Пропускаем onChange если это внешний reset
43
+ if (isExternalResetRef.current) {
44
+ isExternalResetRef.current = false;
45
+ return;
46
+ }
47
+ // Создаем новый debounced вызов
48
+ const debouncedOnChange = debounce(() => {
49
+ onChange(headerFormValues);
50
+ }, 300);
51
+ debouncedOnChangeRef.current = debouncedOnChange;
52
+ debouncedOnChange();
53
+ return () => {
54
+ debouncedOnChange.clear();
55
+ };
56
+ }, [headerFormValues, onChange, hasSubmitButton, isLoading]);
57
+ const searchName = document
58
+ .getElementById(FORM_FILTERS_SEARCH_FIELD_ID)
59
+ ?.getAttribute('name') || '';
60
+ // Исключаем поиск из счетчика фильтров
61
+ const counter = Object.values(headerFormValues).filter(Boolean).length -
62
+ (headerFormValues[searchName] ? 1 : 0);
63
+ const setFiltersValues = (newValues) => headerForm.reset(newValues);
64
+ // Функция отправки формы на SubmitButton. Приводит к вызову onChange без debounce.
65
+ const onSubmit = (submittedValues) => {
66
+ clearPreviousDebouncedOnChange();
67
+ onChange?.(submittedValues);
68
+ };
69
+ // Функция отправки формы на кнопку "Применить" в модальном окне
70
+ const onSubmitModal = (submittedValues) => {
71
+ setFiltersValues(submittedValues);
72
+ onSubmit(submittedValues);
73
+ };
74
+ // Синхронизация внешних values с формой
75
+ useEffect(() => {
76
+ if (isFirstMount) {
77
+ return;
78
+ }
79
+ // Сравниваем значения, чтобы избежать лишних reset
80
+ const valuesChanged = !checkIsDeepEqual(previousValuesRef.current, values);
81
+ if (!valuesChanged) {
82
+ return;
83
+ }
84
+ clearPreviousDebouncedOnChange();
85
+ isExternalResetRef.current = true;
86
+ previousValuesRef.current = values;
87
+ setFiltersValues(values);
88
+ }, [values, headerForm]);
89
+ const allFiltersButtonClassName = useMemo(() => {
90
+ return classNames({
91
+ [formFiltersClassnames.showAllFiltersButtonOnDesktop]: Boolean(secondaryFilters),
92
+ [formFiltersClassnames.showAllFiltersButtonOnMobile]: Boolean(mainFilters) || Boolean(secondaryFilters),
93
+ [formFiltersClassnames.withSearch]: Boolean(searchField),
94
+ [formFiltersClassnames.withoutSearch]: !searchField,
95
+ });
96
+ }, [mainFilters, secondaryFilters, searchField]);
97
+ const dialogProps = {
98
+ validationSchema,
99
+ values,
100
+ mainFilters,
101
+ secondaryFilters,
102
+ defaultValues: headerFormValues,
103
+ onSubmit: onSubmitModal,
104
+ isModalOpen,
105
+ setIsModalOpen,
106
+ };
107
+ return {
108
+ headerForm,
109
+ searchField,
110
+ mainFilters,
111
+ submitButton,
112
+ counter,
113
+ onSubmit,
114
+ onOpenModal,
115
+ dialogProps,
116
+ mainFiltersColumns,
117
+ isLoading,
118
+ allFiltersButtonClassName,
119
+ };
120
+ };
@@ -0,0 +1,10 @@
1
+ import { type FormFiltersModalGroupProps } from './types';
2
+ /**
3
+ * Компонент для группировки фильтров в Grid сетку
4
+ * @example
5
+ * <FormFiltersModalGroup columns="1fr 2fr">
6
+ * <FormSelect />
7
+ * <FormSelect />
8
+ * </FormFiltersModalGroup>
9
+ */
10
+ export declare const FormFiltersModalGroup: ({ children, columns, ...props }: FormFiltersModalGroupProps) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,13 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Container } from './styles';
3
+ /**
4
+ * Компонент для группировки фильтров в Grid сетку
5
+ * @example
6
+ * <FormFiltersModalGroup columns="1fr 2fr">
7
+ * <FormSelect />
8
+ * <FormSelect />
9
+ * </FormFiltersModalGroup>
10
+ */
11
+ export const FormFiltersModalGroup = ({ children, columns = '1fr', ...props }) => {
12
+ return (_jsx(Container, { container: true, columns: columns, ...props, children: children }));
13
+ };
@@ -0,0 +1,2 @@
1
+ export { FormFiltersModalGroup } from './FormFiltersModalGroup';
2
+ export type { FormFiltersModalGroupProps } from './types';
@@ -0,0 +1 @@
1
+ export { FormFiltersModalGroup } from './FormFiltersModalGroup';
@@ -0,0 +1,2 @@
1
+ export { FormFiltersModalGroup } from './FormFiltersModalGroup';
2
+ export type { FormFiltersModalGroupProps } from './types';
@@ -0,0 +1 @@
1
+ export { FormFiltersModalGroup } from './FormFiltersModalGroup';
@@ -0,0 +1,4 @@
1
+ /// <reference types="react" />
2
+ export declare const Container: import("../../components/styled").StyledComponent<Omit<import("../../components/Grid/GridComponent").GridComponentProps, "ref"> & import("react").RefAttributes<HTMLDivElement> & {
3
+ theme?: import("@emotion/react").Theme | undefined;
4
+ } & import("../../components/Grid/styles").StyledGridProps, {}, {}>;
@@ -0,0 +1,23 @@
1
+ import { Grid } from '../../components/Grid';
2
+ import { styled } from '../../components/styled';
3
+ import { formFiltersClassnames } from '../FormFilters/constants';
4
+ export const Container = styled(Grid) `
5
+ display: contents;
6
+
7
+ > * {
8
+ flex: 1;
9
+ }
10
+
11
+ .${formFiltersClassnames.modalFilters} & {
12
+ display: grid;
13
+ column-gap: ${({ theme }) => theme.spacing(5)};
14
+ align-items: center;
15
+ justify-content: space-between;
16
+
17
+ ${({ theme }) => theme.breakpoints.down('sm')} {
18
+ display: flex;
19
+ flex-direction: column;
20
+ align-items: unset;
21
+ }
22
+ }
23
+ `;
@@ -0,0 +1,9 @@
1
+ import type { ReactNode } from 'react';
2
+ import { type GridProps } from '../../components/Grid';
3
+ export type FormFiltersModalGroupProps = Omit<GridProps, 'container'> & {
4
+ children?: ReactNode;
5
+ /**
6
+ * Количество колонок
7
+ */
8
+ columns?: number | string;
9
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,9 @@
1
+ /// <reference types="react" />
2
+ import { type SearchFieldProps } from '../../components/SearchField';
3
+ import { type WithFormFieldProps } from '../types';
4
+ export type FormFiltersSearchFieldProps<TFieldValues extends object> = WithFormFieldProps<SearchFieldProps, TFieldValues> & {
5
+ width?: string;
6
+ };
7
+ export declare const FormFiltersSearchField: <T extends object>(props: Omit<SearchFieldProps, "error" | "name"> & Omit<import("react-hook-form").UseControllerProps<T>, "rules"> & {
8
+ width?: string | undefined;
9
+ } & import("react").RefAttributes<HTMLDivElement>) => import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | null;
@@ -0,0 +1,17 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { forwardRefWithGeneric } from '../../components/forwardRefWithGeneric';
3
+ import { SearchField, } from '../../components/SearchField';
4
+ import { useFormFieldProps } from '../useFormFieldProps';
5
+ import { FORM_FILTERS_SEARCH_FIELD_ID } from './constants';
6
+ import { Wrapper } from './styles';
7
+ /**
8
+ * Адаптер для SearchField
9
+ */
10
+ function FormFiltersSearchFieldInner({ width, ...props }, ref) {
11
+ const fieldProps = useFormFieldProps({
12
+ ...props,
13
+ defaultValue: props.defaultValue || '',
14
+ });
15
+ return (_jsx(Wrapper, { "$width": width, children: _jsx(SearchField, { ...fieldProps, ref: ref, inputProps: { id: FORM_FILTERS_SEARCH_FIELD_ID } }) }));
16
+ }
17
+ export const FormFiltersSearchField = forwardRefWithGeneric(FormFiltersSearchFieldInner);
@@ -0,0 +1 @@
1
+ export declare const FORM_FILTERS_SEARCH_FIELD_ID = "form-filters-search-field";
@@ -0,0 +1 @@
1
+ export const FORM_FILTERS_SEARCH_FIELD_ID = 'form-filters-search-field';
@@ -0,0 +1 @@
1
+ export { FormFiltersSearchField, type FormFiltersSearchFieldProps, } from './FormFiltersSearchField';
@@ -0,0 +1 @@
1
+ export { FormFiltersSearchField, } from './FormFiltersSearchField';
@@ -0,0 +1 @@
1
+ export { FormFiltersSearchField, type FormFiltersSearchFieldProps, } from './FormFiltersSearchField';
@@ -0,0 +1 @@
1
+ export { FormFiltersSearchField, } from './FormFiltersSearchField';
@@ -0,0 +1,7 @@
1
+ /// <reference types="react" />
2
+ export declare const Wrapper: import("../../components/styled").StyledComponent<{
3
+ theme?: import("@emotion/react").Theme | undefined;
4
+ as?: import("react").ElementType<any, keyof import("react").JSX.IntrinsicElements> | undefined;
5
+ } & {
6
+ $width?: string | undefined;
7
+ }, import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, {}>;
@@ -0,0 +1,8 @@
1
+ import { styled } from '../../components/styled';
2
+ export const Wrapper = styled.div `
3
+ min-width: ${({ $width }) => $width || '240px'};
4
+
5
+ ${({ theme }) => theme.breakpoints.down('sm')} {
6
+ width: 100%;
7
+ }
8
+ `;
@@ -7,6 +7,7 @@ export declare const useLogic: <TFormValues extends Record<string, unknown>>({ a
7
7
  title?: import("react").ReactNode;
8
8
  description?: import("react").ReactNode;
9
9
  subheader?: import("react").ReactNode;
10
+ filters?: import("react").ReactNode;
10
11
  breadcrumbs?: import("react").ReactNode;
11
12
  className?: string | undefined;
12
13
  isLoading?: boolean | undefined;
@@ -0,0 +1,5 @@
1
+ /// <reference types="react" />
2
+ import { type SearchFieldProps } from '../../components/SearchField';
3
+ import { type WithFormFieldProps } from '../types';
4
+ export type FormSearchFieldProps<TFieldValues extends object> = WithFormFieldProps<SearchFieldProps, TFieldValues>;
5
+ export declare const FormSearchField: <T extends object>(props: Omit<SearchFieldProps, "error" | "name"> & Omit<import("react-hook-form").UseControllerProps<T>, "rules"> & import("react").RefAttributes<HTMLDivElement>) => import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | null;
@@ -0,0 +1,15 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { forwardRefWithGeneric } from '../../components/forwardRefWithGeneric';
3
+ import { SearchField, } from '../../components/SearchField';
4
+ import { useFormFieldProps } from '../useFormFieldProps';
5
+ /**
6
+ * Адаптер для SearchField
7
+ */
8
+ function FormSearchFieldInner(props, ref) {
9
+ const fieldProps = useFormFieldProps({
10
+ ...props,
11
+ defaultValue: props.defaultValue || '',
12
+ });
13
+ return _jsx(SearchField, { ...fieldProps, ref: ref });
14
+ }
15
+ export const FormSearchField = forwardRefWithGeneric(FormSearchFieldInner);
@@ -0,0 +1 @@
1
+ export { FormSearchField, type FormSearchFieldProps, } from './FormSearchField';
@@ -0,0 +1 @@
1
+ export { FormSearchField, } from './FormSearchField';
@@ -0,0 +1 @@
1
+ export { FormSearchField, type FormSearchFieldProps, } from './FormSearchField';
@@ -0,0 +1 @@
1
+ export { FormSearchField, } from './FormSearchField';
@@ -1,5 +1,5 @@
1
1
  import type { DrawerProps as MuiDrawerProps } from '@mui/material/Drawer';
2
- import { type ReactElement } from 'react';
2
+ import { type ReactElement, type ReactNode } from 'react';
3
3
  import { type WithoutEmotionSpecific } from '../types/WithoutEmotionSpecific';
4
4
  export type BottomDrawerProps = {
5
5
  /**
@@ -11,5 +11,9 @@ export type BottomDrawerProps = {
11
11
  * * @default 56
12
12
  */
13
13
  drawerHeaderHeight?: number;
14
+ /**
15
+ * Дополнительный контент хедера
16
+ */
17
+ headerContent?: ReactNode;
14
18
  } & WithoutEmotionSpecific<Omit<MuiDrawerProps, 'anchor' | 'variant' | 'title'>>;
15
- export declare const BottomDrawer: ({ title, drawerHeaderHeight, children, onClose, ...props }: BottomDrawerProps) => import("react/jsx-runtime").JSX.Element;
19
+ export declare const BottomDrawer: ({ title, drawerHeaderHeight, children, onClose, headerContent, ...props }: BottomDrawerProps) => import("react/jsx-runtime").JSX.Element;
@@ -6,12 +6,12 @@ const CrossOutlineMd_1 = require("../../icons/CrossOutlineMd");
6
6
  const IconButton_1 = require("../IconButton");
7
7
  const constants_1 = require("./constants");
8
8
  const styles_1 = require("./styles");
9
- const BottomDrawer = ({ title, drawerHeaderHeight = constants_1.DEFAULT_HEADER_HEIGHT, children, onClose, ...props }) => {
9
+ const BottomDrawer = ({ title, drawerHeaderHeight = constants_1.DEFAULT_HEADER_HEIGHT, children, onClose, headerContent, ...props }) => {
10
10
  const handleClose = (event) => {
11
11
  if (onClose) {
12
12
  onClose(event, 'escapeKeyDown');
13
13
  }
14
14
  };
15
- return ((0, jsx_runtime_1.jsxs)(styles_1.StyledDrawer, { ...props, anchor: "bottom", onClose: onClose, children: [(0, jsx_runtime_1.jsxs)(styles_1.Header, { drawerHeaderHeight: drawerHeaderHeight, children: [(0, jsx_runtime_1.jsx)(styles_1.HeaderTitle, { variant: "h6", noWrap: true, children: title }), (0, jsx_runtime_1.jsx)(IconButton_1.IconButton, { variant: "text", onClick: handleClose, size: "large", children: (0, jsx_runtime_1.jsx)(CrossOutlineMd_1.CrossOutlineMd, {}) })] }), (0, jsx_runtime_1.jsx)(styles_1.Body, { className: constants_1.bottomDrawerClassnames.content, children: children })] }));
15
+ return ((0, jsx_runtime_1.jsxs)(styles_1.StyledDrawer, { ...props, anchor: "bottom", onClose: onClose, children: [(0, jsx_runtime_1.jsxs)(styles_1.Header, { drawerHeaderHeight: drawerHeaderHeight, children: [(0, jsx_runtime_1.jsx)(styles_1.HeaderTitle, { variant: "h6", noWrap: true, children: title }), headerContent, (0, jsx_runtime_1.jsx)(IconButton_1.IconButton, { variant: "text", onClick: handleClose, size: "large", children: (0, jsx_runtime_1.jsx)(CrossOutlineMd_1.CrossOutlineMd, {}) })] }), (0, jsx_runtime_1.jsx)(styles_1.Body, { className: constants_1.bottomDrawerClassnames.content, children: children })] }));
16
16
  };
17
17
  exports.BottomDrawer = BottomDrawer;
@@ -15,6 +15,7 @@ export declare const Title: import("@emotion/styled/dist/declarations/src/types"
15
15
  export declare const StyledBottomDrawer: import("@emotion/styled/dist/declarations/src/types").StyledComponent<{
16
16
  title?: string | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>[] | undefined;
17
17
  drawerHeaderHeight?: number | undefined;
18
+ headerContent?: import("react").ReactNode;
18
19
  } & import("..").WithoutEmotionSpecific<Omit<import("@mui/material").DrawerProps, "title" | "anchor" | "variant">> & {
19
20
  theme?: import("@emotion/react").Theme | undefined;
20
21
  }, {}, {}>;
@@ -1,5 +1,5 @@
1
1
  import type { DialogProps as MuiDialogProps } from '@mui/material/Dialog';
2
- import { type ReactElement } from 'react';
2
+ import { type ReactElement, type ReactNode } from 'react';
3
3
  import type { WithoutEmotionSpecific } from '../types/WithoutEmotionSpecific';
4
4
  export type DialogSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
5
5
  export type DialogProps = WithoutEmotionSpecific<Omit<MuiDialogProps, 'title' | 'maxWidth'>> & {
@@ -16,5 +16,9 @@ export type DialogProps = WithoutEmotionSpecific<Omit<MuiDialogProps, 'title' |
16
16
  * @default md
17
17
  */
18
18
  size?: DialogSize;
19
+ /**
20
+ * Дополнительный контент хедера
21
+ */
22
+ headerContent?: ReactNode;
19
23
  };
20
- export declare const Dialog: ({ children, title, disableBackdropClick, onClose, size, ...props }: DialogProps) => import("react/jsx-runtime").JSX.Element;
24
+ export declare const Dialog: ({ children, title, disableBackdropClick, onClose, size, headerContent, ...props }: DialogProps) => import("react/jsx-runtime").JSX.Element;
@@ -6,7 +6,7 @@ const BottomDrawer_1 = require("../BottomDrawer");
6
6
  const DialogTitle_1 = require("../DialogTitle");
7
7
  const useViewportType_1 = require("../useViewportType");
8
8
  const styles_1 = require("./styles");
9
- const Dialog = ({ children, title, disableBackdropClick, onClose, size = 'md', ...props }) => {
9
+ const Dialog = ({ children, title, disableBackdropClick, onClose, size = 'md', headerContent, ...props }) => {
10
10
  const handleClose = onClose &&
11
11
  ((event, reason) => {
12
12
  if (disableBackdropClick && reason === 'backdropClick') {
@@ -16,8 +16,8 @@ const Dialog = ({ children, title, disableBackdropClick, onClose, size = 'md', .
16
16
  });
17
17
  const { isMobile } = (0, useViewportType_1.useViewportType)();
18
18
  if (isMobile) {
19
- return ((0, jsx_runtime_1.jsx)(BottomDrawer_1.BottomDrawer, { onClose: handleClose, title: title, ...props, children: children }));
19
+ return ((0, jsx_runtime_1.jsx)(BottomDrawer_1.BottomDrawer, { onClose: handleClose, title: title, headerContent: headerContent, ...props, children: children }));
20
20
  }
21
- return ((0, jsx_runtime_1.jsxs)(styles_1.StyledDialog, { "$size": size, onClose: handleClose, ...props, children: [title && (0, jsx_runtime_1.jsx)(DialogTitle_1.DialogTitle, { onClose: onClose, children: title }), children] }));
21
+ return ((0, jsx_runtime_1.jsxs)(styles_1.StyledDialog, { "$size": size, onClose: handleClose, ...props, children: [title && ((0, jsx_runtime_1.jsx)(DialogTitle_1.DialogTitle, { onClose: onClose, headerContent: headerContent, children: title })), children] }));
22
22
  };
23
23
  exports.Dialog = Dialog;