@astral/ui 4.113.2 → 4.114.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 (171) hide show
  1. package/components/ActionGroup/ActionGroup.js +2 -2
  2. package/components/ActionGroup/SecondaryActions/SecondaryActions.d.ts +1 -1
  3. package/components/ActionGroup/SecondaryActions/SecondaryActions.js +2 -2
  4. package/components/ActionGroup/types.d.ts +9 -0
  5. package/components/DataGrid/DataGrid.js +13 -5
  6. package/components/DataGrid/DataGridContext/DataGridProvider/DataGridProvider.d.ts +3 -2
  7. package/components/DataGrid/DataGridContext/DataGridProvider/DataGridProvider.js +3 -2
  8. package/components/DataGrid/DataGridSelectionContext/DataGridSelectionContext.d.ts +25 -0
  9. package/components/DataGrid/DataGridSelectionContext/DataGridSelectionContext.js +13 -0
  10. package/components/DataGrid/DataGridSelectionContext/DataGridSelectionProvider/DataGridSelectionProvider.d.ts +7 -0
  11. package/components/DataGrid/DataGridSelectionContext/DataGridSelectionProvider/DataGridSelectionProvider.js +8 -0
  12. package/components/DataGrid/DataGridSelectionContext/DataGridSelectionProvider/index.d.ts +1 -0
  13. package/components/DataGrid/DataGridSelectionContext/DataGridSelectionProvider/index.js +1 -0
  14. package/components/DataGrid/DataGridSelectionContext/index.d.ts +2 -0
  15. package/components/DataGrid/DataGridSelectionContext/index.js +2 -0
  16. package/components/DataGrid/constants.d.ts +2 -0
  17. package/components/DataGrid/constants.js +2 -0
  18. package/components/DataGrid/styles.d.ts +4 -0
  19. package/components/DataGrid/styles.js +26 -0
  20. package/components/DataGrid/types.d.ts +7 -0
  21. package/components/DataGrid/useLogic/useLogic.d.ts +3 -1
  22. package/components/DataGrid/useLogic/useLogic.js +10 -2
  23. package/components/DataGrid/utils/checkIsMassActionsOpen/checkIsMassActionsOpen.d.ts +10 -0
  24. package/components/DataGrid/utils/checkIsMassActionsOpen/checkIsMassActionsOpen.js +4 -0
  25. package/components/DataGrid/utils/checkIsMassActionsOpen/index.d.ts +1 -0
  26. package/components/DataGrid/utils/checkIsMassActionsOpen/index.js +1 -0
  27. package/components/DataGrid/utils/index.d.ts +1 -0
  28. package/components/DataGrid/utils/index.js +1 -0
  29. package/components/DataGridInfinite/DataGridInfinite.js +17 -13
  30. package/components/DataGridInfinite/Footer/Footer.d.ts +6 -0
  31. package/components/DataGridInfinite/Footer/Footer.js +25 -0
  32. package/components/DataGridInfinite/Footer/index.d.ts +2 -0
  33. package/components/DataGridInfinite/Footer/index.js +1 -0
  34. package/components/DataGridInfinite/Footer/types.d.ts +26 -0
  35. package/components/DataGridInfinite/Footer/types.js +1 -0
  36. package/components/DataGridInfinite/constants.d.ts +1 -0
  37. package/components/DataGridInfinite/constants.js +1 -0
  38. package/components/DataGridInfinite/styles.d.ts +4 -0
  39. package/components/DataGridInfinite/styles.js +24 -0
  40. package/components/DataGridInfinite/useLogic/useLogic.d.ts +6 -3
  41. package/components/DataGridInfinite/useLogic/useLogic.js +32 -8
  42. package/components/DataGridMassActions/DataGridMassActions.d.ts +15 -0
  43. package/components/DataGridMassActions/DataGridMassActions.js +32 -0
  44. package/components/DataGridMassActions/constants.d.ts +4 -0
  45. package/components/DataGridMassActions/constants.js +5 -0
  46. package/components/DataGridMassActions/index.d.ts +2 -0
  47. package/components/DataGridMassActions/index.js +1 -0
  48. package/components/DataGridMassActions/public.d.ts +2 -0
  49. package/components/DataGridMassActions/public.js +1 -0
  50. package/components/DataGridMassActions/styles.d.ts +8 -0
  51. package/components/DataGridMassActions/styles.js +49 -0
  52. package/components/DataGridMassActions/tests/testDataGridMassActions.d.ts +23 -0
  53. package/components/DataGridMassActions/tests/testDataGridMassActions.js +77 -0
  54. package/components/DataGridMassActions/types.d.ts +2 -0
  55. package/components/DataGridMassActions/types.js +1 -0
  56. package/components/DataGridMassActions/useLogic/index.d.ts +1 -0
  57. package/components/DataGridMassActions/useLogic/index.js +1 -0
  58. package/components/DataGridMassActions/useLogic/useLogic.d.ts +6 -0
  59. package/components/DataGridMassActions/useLogic/useLogic.js +21 -0
  60. package/components/IconDropdownButton/IconDropdownButton.d.ts +5 -0
  61. package/components/IconDropdownButton/IconDropdownButton.js +2 -2
  62. package/components/MassActions/MassActions.d.ts +5 -0
  63. package/components/MassActions/MassActions.js +17 -0
  64. package/components/MassActions/constants.d.ts +6 -0
  65. package/components/MassActions/constants.js +12 -0
  66. package/components/MassActions/index.d.ts +2 -0
  67. package/components/MassActions/index.js +1 -0
  68. package/components/MassActions/styles.d.ts +34 -0
  69. package/components/MassActions/styles.js +43 -0
  70. package/components/MassActions/types.d.ts +20 -0
  71. package/components/MassActions/types.js +1 -0
  72. package/components/MassActions/useLogic/index.d.ts +1 -0
  73. package/components/MassActions/useLogic/index.js +1 -0
  74. package/components/MassActions/useLogic/useLogic.d.ts +7 -0
  75. package/components/MassActions/useLogic/useLogic.js +9 -0
  76. package/components/PageHeader/PageActions/SecondaryActions/styles.d.ts +1 -1
  77. package/components/Tree/TreeItem/MobileSlidePanel/MobileSlidePanelContent/useLogic/hooks/index.d.ts +0 -1
  78. package/components/Tree/TreeItem/MobileSlidePanel/MobileSlidePanelContent/useLogic/hooks/index.js +0 -1
  79. package/components/Tree/TreeItem/MobileSlidePanel/MobileSlidePanelContent/useLogic/useLogic.js +2 -1
  80. package/hook-form/FormToggleButtonGroup/FormToggleButtonGroup.d.ts +1 -1
  81. package/hook-form/FormToggleButtonGroup/FormToggleButtonGroup.js +2 -2
  82. package/node/components/ActionGroup/ActionGroup.js +2 -2
  83. package/node/components/ActionGroup/SecondaryActions/SecondaryActions.d.ts +1 -1
  84. package/node/components/ActionGroup/SecondaryActions/SecondaryActions.js +2 -2
  85. package/node/components/ActionGroup/types.d.ts +9 -0
  86. package/node/components/DataGrid/DataGrid.js +12 -4
  87. package/node/components/DataGrid/DataGridContext/DataGridProvider/DataGridProvider.d.ts +3 -2
  88. package/node/components/DataGrid/DataGridContext/DataGridProvider/DataGridProvider.js +3 -2
  89. package/node/components/DataGrid/DataGridSelectionContext/DataGridSelectionContext.d.ts +25 -0
  90. package/node/components/DataGrid/DataGridSelectionContext/DataGridSelectionContext.js +16 -0
  91. package/node/components/DataGrid/DataGridSelectionContext/DataGridSelectionProvider/DataGridSelectionProvider.d.ts +7 -0
  92. package/node/components/DataGrid/DataGridSelectionContext/DataGridSelectionProvider/DataGridSelectionProvider.js +12 -0
  93. package/node/components/DataGrid/DataGridSelectionContext/DataGridSelectionProvider/index.d.ts +1 -0
  94. package/node/components/DataGrid/DataGridSelectionContext/DataGridSelectionProvider/index.js +17 -0
  95. package/node/components/DataGrid/DataGridSelectionContext/index.d.ts +2 -0
  96. package/node/components/DataGrid/DataGridSelectionContext/index.js +18 -0
  97. package/node/components/DataGrid/constants.d.ts +2 -0
  98. package/node/components/DataGrid/constants.js +2 -0
  99. package/node/components/DataGrid/styles.d.ts +4 -0
  100. package/node/components/DataGrid/styles.js +27 -1
  101. package/node/components/DataGrid/types.d.ts +7 -0
  102. package/node/components/DataGrid/useLogic/useLogic.d.ts +3 -1
  103. package/node/components/DataGrid/useLogic/useLogic.js +9 -1
  104. package/node/components/DataGrid/utils/checkIsMassActionsOpen/checkIsMassActionsOpen.d.ts +10 -0
  105. package/node/components/DataGrid/utils/checkIsMassActionsOpen/checkIsMassActionsOpen.js +8 -0
  106. package/node/components/DataGrid/utils/checkIsMassActionsOpen/index.d.ts +1 -0
  107. package/node/components/DataGrid/utils/checkIsMassActionsOpen/index.js +5 -0
  108. package/node/components/DataGrid/utils/index.d.ts +1 -0
  109. package/node/components/DataGrid/utils/index.js +3 -1
  110. package/node/components/DataGridInfinite/DataGridInfinite.js +19 -15
  111. package/node/components/DataGridInfinite/Footer/Footer.d.ts +6 -0
  112. package/node/components/DataGridInfinite/Footer/Footer.js +29 -0
  113. package/node/components/DataGridInfinite/Footer/index.d.ts +2 -0
  114. package/node/components/DataGridInfinite/Footer/index.js +17 -0
  115. package/node/components/DataGridInfinite/Footer/types.d.ts +26 -0
  116. package/node/components/DataGridInfinite/Footer/types.js +2 -0
  117. package/node/components/DataGridInfinite/constants.d.ts +1 -0
  118. package/node/components/DataGridInfinite/constants.js +1 -0
  119. package/node/components/DataGridInfinite/styles.d.ts +4 -0
  120. package/node/components/DataGridInfinite/styles.js +27 -3
  121. package/node/components/DataGridInfinite/useLogic/useLogic.d.ts +6 -3
  122. package/node/components/DataGridInfinite/useLogic/useLogic.js +30 -6
  123. package/node/components/DataGridMassActions/DataGridMassActions.d.ts +15 -0
  124. package/node/components/DataGridMassActions/DataGridMassActions.js +36 -0
  125. package/node/components/DataGridMassActions/constants.d.ts +4 -0
  126. package/node/components/DataGridMassActions/constants.js +8 -0
  127. package/node/components/DataGridMassActions/index.d.ts +2 -0
  128. package/node/components/DataGridMassActions/index.js +5 -0
  129. package/node/components/DataGridMassActions/public.d.ts +2 -0
  130. package/node/components/DataGridMassActions/public.js +5 -0
  131. package/node/components/DataGridMassActions/styles.d.ts +8 -0
  132. package/node/components/DataGridMassActions/styles.js +52 -0
  133. package/node/components/DataGridMassActions/tests/testDataGridMassActions.d.ts +23 -0
  134. package/node/components/DataGridMassActions/tests/testDataGridMassActions.js +81 -0
  135. package/node/components/DataGridMassActions/types.d.ts +2 -0
  136. package/node/components/DataGridMassActions/types.js +2 -0
  137. package/node/components/DataGridMassActions/useLogic/index.d.ts +1 -0
  138. package/node/components/DataGridMassActions/useLogic/index.js +17 -0
  139. package/node/components/DataGridMassActions/useLogic/useLogic.d.ts +6 -0
  140. package/node/components/DataGridMassActions/useLogic/useLogic.js +25 -0
  141. package/node/components/IconDropdownButton/IconDropdownButton.d.ts +5 -0
  142. package/node/components/IconDropdownButton/IconDropdownButton.js +2 -2
  143. package/node/components/MassActions/MassActions.d.ts +5 -0
  144. package/node/components/MassActions/MassActions.js +21 -0
  145. package/node/components/MassActions/constants.d.ts +6 -0
  146. package/node/components/MassActions/constants.js +15 -0
  147. package/node/components/MassActions/index.d.ts +2 -0
  148. package/node/components/MassActions/index.js +5 -0
  149. package/node/components/MassActions/styles.d.ts +34 -0
  150. package/node/components/MassActions/styles.js +46 -0
  151. package/node/components/MassActions/types.d.ts +20 -0
  152. package/node/components/MassActions/types.js +2 -0
  153. package/node/components/MassActions/useLogic/index.d.ts +1 -0
  154. package/node/components/MassActions/useLogic/index.js +17 -0
  155. package/node/components/MassActions/useLogic/useLogic.d.ts +7 -0
  156. package/node/components/MassActions/useLogic/useLogic.js +13 -0
  157. package/node/components/PageHeader/PageActions/SecondaryActions/styles.d.ts +1 -1
  158. package/node/components/Tree/TreeItem/MobileSlidePanel/MobileSlidePanelContent/useLogic/hooks/index.d.ts +0 -1
  159. package/node/components/Tree/TreeItem/MobileSlidePanel/MobileSlidePanelContent/useLogic/hooks/index.js +0 -1
  160. package/node/components/Tree/TreeItem/MobileSlidePanel/MobileSlidePanelContent/useLogic/useLogic.js +2 -1
  161. package/node/hook-form/FormToggleButtonGroup/FormToggleButtonGroup.d.ts +1 -1
  162. package/node/hook-form/FormToggleButtonGroup/FormToggleButtonGroup.js +2 -2
  163. package/package.json +1 -1
  164. /package/components/{Tree/TreeItem/MobileSlidePanel/MobileSlidePanelContent/useLogic/hooks/useDelayedUnmount → useDelayedUnmount}/index.d.ts +0 -0
  165. /package/components/{Tree/TreeItem/MobileSlidePanel/MobileSlidePanelContent/useLogic/hooks/useDelayedUnmount → useDelayedUnmount}/index.js +0 -0
  166. /package/components/{Tree/TreeItem/MobileSlidePanel/MobileSlidePanelContent/useLogic/hooks/useDelayedUnmount → useDelayedUnmount}/useDelayedUnmount.d.ts +0 -0
  167. /package/components/{Tree/TreeItem/MobileSlidePanel/MobileSlidePanelContent/useLogic/hooks/useDelayedUnmount → useDelayedUnmount}/useDelayedUnmount.js +0 -0
  168. /package/node/components/{Tree/TreeItem/MobileSlidePanel/MobileSlidePanelContent/useLogic/hooks/useDelayedUnmount → useDelayedUnmount}/index.d.ts +0 -0
  169. /package/node/components/{Tree/TreeItem/MobileSlidePanel/MobileSlidePanelContent/useLogic/hooks/useDelayedUnmount → useDelayedUnmount}/index.js +0 -0
  170. /package/node/components/{Tree/TreeItem/MobileSlidePanel/MobileSlidePanelContent/useLogic/hooks/useDelayedUnmount → useDelayedUnmount}/useDelayedUnmount.d.ts +0 -0
  171. /package/node/components/{Tree/TreeItem/MobileSlidePanel/MobileSlidePanelContent/useLogic/hooks/useDelayedUnmount → useDelayedUnmount}/useDelayedUnmount.js +0 -0
@@ -2,9 +2,13 @@ import { type ChangeEvent } from 'react';
2
2
  import type { ListRange } from 'react-virtuoso';
3
3
  import { type DataGridRow } from '../../DataGrid';
4
4
  import { type CellValue } from '../../DataGrid/types';
5
+ import { type FooterContext } from '../Footer';
5
6
  import { type DataGridInfiniteProps, type DataGridInfiniteVirtuosoHandle } from '../types';
6
7
  type UseLogicParams<TData extends Record<string, CellValue> = DataGridRow, TSortField extends keyof TData = keyof TData> = DataGridInfiniteProps<TData, TSortField>;
7
- export declare const useLogic: <TData extends Record<string, unknown> = DataGridRow, TSortField extends keyof TData = keyof TData>({ keyId, columns, rows, variant, tree, subrows, selectedRows, maxHeight, isLoading, isError, isDisabled, isEndReached, onEndReached, onStartReached, isStartReached, onSelectRow, isHideSelectAll, initialVisibleItemIndex, isHideHeaderIfNoData, gridViewMode: gridViewModeProp, mobileDisplayVariant, virtuosoRef, }: UseLogicParams<TData, TSortField>) => {
8
+ export declare const useLogic: <TData extends Record<string, unknown> = DataGridRow, TSortField extends keyof TData = keyof TData>({ keyId, columns, rows, variant, tree, subrows, selectedRows, maxHeight, isLoading, isError, isDisabled, isEndReached, endOfScrollMsg, onRetry, onEndReached, onStartReached, isStartReached, onSelectRow, isHideSelectAll, initialVisibleItemIndex, isHideHeaderIfNoData, gridViewMode: gridViewModeProp, massActions, mobileDisplayVariant, virtuosoRef, }: UseLogicParams<TData, TSortField>) => {
9
+ footerContext: FooterContext;
10
+ hasMassActions: boolean;
11
+ isMassActionsOpen: boolean;
8
12
  containerCssVars: import("react").CSSProperties;
9
13
  mobileSkeletonProps: {
10
14
  cellsCount: number;
@@ -19,7 +23,6 @@ export declare const useLogic: <TData extends Record<string, unknown> = DataGrid
19
23
  expandedLevel?: number | undefined;
20
24
  initialVisibleChildrenCount?: number | undefined;
21
25
  } | undefined;
22
- isEndReached: boolean | undefined;
23
26
  isHideHead: boolean;
24
27
  hasGroupedColumns: boolean;
25
28
  gridViewMode: "standard" | "full";
@@ -32,7 +35,7 @@ export declare const useLogic: <TData extends Record<string, unknown> = DataGrid
32
35
  ref: (node: DataGridInfiniteVirtuosoHandle | null) => void;
33
36
  endReached: () => void;
34
37
  rangeChanged: (range: ListRange) => void;
35
- totalListHeightChanged: () => void;
38
+ totalListHeightChanged: (listHeight: number) => void;
36
39
  startReached: () => void;
37
40
  firstItemIndex: number;
38
41
  };
@@ -2,12 +2,12 @@ import { useCallback, useEffect, useMemo, useRef, useState, } from 'react';
2
2
  import { EXPANDED_LEVEL_BY_DEFAULT, INITIAL_OPENED_NESTED_CHILDREN_COUNT_ON_MOBILE_BY_DEFAULT, } from '../../DataGrid/constants';
3
3
  import { Variant } from '../../DataGrid/enums';
4
4
  import { useDataGridMobileLogic } from '../../DataGrid/useLogic/hooks';
5
- import { getDataGridCssVars, getFlattenColumns, getGridTemplateColumns, isGroupColumns, } from '../../DataGrid/utils';
5
+ import { checkIsMassActionsOpen, getDataGridCssVars, getFlattenColumns, getGridTemplateColumns, isGroupColumns, } from '../../DataGrid/utils';
6
6
  import { useViewportType } from '../../useViewportType';
7
7
  import { prop } from '../../utils/prop';
8
8
  import { uniqueBy } from '../../utils/uniqueBy';
9
- import { dataGridInfiniteClassnames, VIRTUOSO_CONTAINER_ID, VIRTUOSO_LIST_ID, } from '../constants';
10
- export const useLogic = ({ keyId, columns, rows, variant, tree, subrows, selectedRows = [], maxHeight, isLoading, isError, isDisabled, isEndReached, onEndReached, onStartReached, isStartReached, onSelectRow, isHideSelectAll, initialVisibleItemIndex, isHideHeaderIfNoData = false, gridViewMode: gridViewModeProp = 'standard', mobileDisplayVariant = variant === 'subrows' ? 'cards' : 'list', virtuosoRef, }) => {
9
+ import { dataGridInfiniteClassnames, VIRTUOSO_CONTAINER_ID, } from '../constants';
10
+ export const useLogic = ({ keyId, columns, rows, variant, tree, subrows, selectedRows = [], maxHeight, isLoading, isError, isDisabled, isEndReached, endOfScrollMsg, onRetry, onEndReached, onStartReached, isStartReached, onSelectRow, isHideSelectAll, initialVisibleItemIndex, isHideHeaderIfNoData = false, gridViewMode: gridViewModeProp = 'standard', massActions, mobileDisplayVariant = variant === 'subrows' ? 'cards' : 'list', virtuosoRef, }) => {
11
11
  const virtuoso = useRef(null);
12
12
  const handleVirtuosoRef = useCallback((node) => {
13
13
  virtuoso.current = node;
@@ -112,11 +112,11 @@ export const useLogic = ({ keyId, columns, rows, variant, tree, subrows, selecte
112
112
  const handleScrollToTop = () => {
113
113
  virtuoso.current?.scrollIntoView({ index: 0, behavior: 'smooth' });
114
114
  };
115
- const recalculateVerticalScroll = () => {
115
+ const recalculateVerticalScroll = (listHeight) => {
116
116
  const container = document.getElementById(VIRTUOSO_CONTAINER_ID);
117
- const list = document.getElementById(VIRTUOSO_LIST_ID);
118
- if (container && list) {
119
- setHasVerticalScroll(list?.clientHeight > container?.clientHeight);
117
+ if (container) {
118
+ const massActionsSpacerHeight = container.querySelector(`.${dataGridInfiniteClassnames.massActionsSpacer}`)?.clientHeight ?? 0;
119
+ setHasVerticalScroll(listHeight - massActionsSpacerHeight > container.clientHeight);
120
120
  }
121
121
  };
122
122
  const isHideHead = isHideHeaderIfNoData && rows.length === 0;
@@ -130,14 +130,38 @@ export const useLogic = ({ keyId, columns, rows, variant, tree, subrows, selecte
130
130
  const isNotRenderHeader = (!isLoading && !isError && !isStartReached) ||
131
131
  !onStartReached ||
132
132
  isStartReached;
133
+ const hasMassActions = Boolean(massActions);
134
+ const isMassActionsOpen = checkIsMassActionsOpen({
135
+ hasMassActions,
136
+ selectedRowsCount: selectedRows.length,
137
+ isMobile,
138
+ });
139
+ const footerContext = useMemo(() => ({
140
+ isLoading,
141
+ isError,
142
+ isEndReached: isEndReached && hasVerticalScroll,
143
+ endOfScrollMsg,
144
+ onRetry,
145
+ hasMassActions,
146
+ }), [
147
+ isLoading,
148
+ isError,
149
+ isEndReached,
150
+ hasVerticalScroll,
151
+ endOfScrollMsg,
152
+ onRetry,
153
+ hasMassActions,
154
+ ]);
133
155
  return {
156
+ footerContext,
157
+ hasMassActions,
158
+ isMassActionsOpen,
134
159
  containerCssVars,
135
160
  mobileSkeletonProps,
136
161
  isNoData,
137
162
  isMobile,
138
163
  isDataGridDisabled,
139
164
  treeRenderConfig,
140
- isEndReached: isEndReached && hasVerticalScroll,
141
165
  isHideHead,
142
166
  hasGroupedColumns,
143
167
  gridViewMode,
@@ -0,0 +1,15 @@
1
+ import { type DataGridMassActionsProps } from './types';
2
+ /**
3
+ * Панель массовых действий над выбранными строками `DataGrid` и `DataGridInfinite`.
4
+ *
5
+ * @example
6
+ * <DataGrid
7
+ * massActions={
8
+ * <DataGridMassActions
9
+ * entityDeclension={['документ', 'документа', 'документов']}
10
+ * main={[{ text: 'Отправить', onClick: handleSend }]}
11
+ * />
12
+ * }
13
+ * />
14
+ */
15
+ export declare const DataGridMassActions: ({ className, ...props }: DataGridMassActionsProps) => import("react/jsx-runtime").JSX.Element | null;
@@ -0,0 +1,32 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { cva } from '../utils/cva';
3
+ import { dataGridMassActionsClassnames } from './constants';
4
+ import { StyledMassActions } from './styles';
5
+ import { useLogic } from './useLogic';
6
+ const rootCva = cva(dataGridMassActionsClassnames.root, {
7
+ variants: {
8
+ isOpen: {
9
+ true: dataGridMassActionsClassnames.open,
10
+ },
11
+ },
12
+ });
13
+ /**
14
+ * Панель массовых действий над выбранными строками `DataGrid` и `DataGridInfinite`.
15
+ *
16
+ * @example
17
+ * <DataGrid
18
+ * massActions={
19
+ * <DataGridMassActions
20
+ * entityDeclension={['документ', 'документа', 'документов']}
21
+ * main={[{ text: 'Отправить', onClick: handleSend }]}
22
+ * />
23
+ * }
24
+ * />
25
+ */
26
+ export const DataGridMassActions = ({ className, ...props }) => {
27
+ const { isOpen, isMounted, selectedCount, handleClose } = useLogic();
28
+ if (!isOpen && !isMounted) {
29
+ return null;
30
+ }
31
+ return (_jsx(StyledMassActions, { ...props, className: rootCva({ isOpen, className }), selectedCount: selectedCount, onClose: handleClose }));
32
+ };
@@ -0,0 +1,4 @@
1
+ export declare const dataGridMassActionsClassnames: {
2
+ root: string;
3
+ open: string;
4
+ };
@@ -0,0 +1,5 @@
1
+ import { createUIKitClassname } from '../utils/createUIKitClassname';
2
+ export const dataGridMassActionsClassnames = {
3
+ root: createUIKitClassname('data-grid-mass-actions'),
4
+ open: createUIKitClassname('data-grid-mass-actions_open'),
5
+ };
@@ -0,0 +1,2 @@
1
+ export { DataGridMassActions } from './DataGridMassActions';
2
+ export type { DataGridMassActionsProps } from './types';
@@ -0,0 +1 @@
1
+ export { DataGridMassActions } from './DataGridMassActions';
@@ -0,0 +1,2 @@
1
+ export { DataGridMassActions } from './DataGridMassActions';
2
+ export type { DataGridMassActionsProps } from './types';
@@ -0,0 +1 @@
1
+ export { DataGridMassActions } from './DataGridMassActions';
@@ -0,0 +1,8 @@
1
+ export declare const StyledMassActions: import("../styled").StyledComponent<Pick<import("..").ActionGroupProps, "main" | "secondary"> & {
2
+ selectedCount: number;
3
+ entityDeclension: [string, string, string];
4
+ onClose: () => void;
5
+ className?: string | undefined;
6
+ } & {
7
+ theme?: import("@emotion/react").Theme | undefined;
8
+ }, {}, {}>;
@@ -0,0 +1,49 @@
1
+ import { MassActions } from '../MassActions';
2
+ import { keyframes, styled } from '../styled';
3
+ import { dataGridMassActionsClassnames } from './constants';
4
+ const slideIn = keyframes `
5
+ from {
6
+ transform: translateY(100%);
7
+
8
+ opacity: 0;
9
+ }
10
+
11
+ to {
12
+ transform: translateY(0);
13
+
14
+ opacity: 1;
15
+ }
16
+ `;
17
+ const slideOut = keyframes `
18
+ from {
19
+ transform: translateY(0);
20
+
21
+ opacity: 1;
22
+ }
23
+
24
+ to {
25
+ transform: translateY(100%);
26
+
27
+ opacity: 0;
28
+ }
29
+ `;
30
+ export const StyledMassActions = styled(MassActions) `
31
+ position: absolute;
32
+ z-index: ${({ theme }) => theme.zIndex.appBar};
33
+ right: 0;
34
+ bottom: ${({ theme }) => theme.spacing(3)};
35
+ left: 0;
36
+
37
+ max-width: calc(100% - ${({ theme }) => theme.spacing(8)});
38
+ margin-inline: auto;
39
+
40
+ animation: ${slideOut}
41
+ ${({ theme }) => theme.transitions.duration.leavingScreen}ms
42
+ ${({ theme }) => theme.transitions.easing.easeIn} forwards;
43
+
44
+ &.${dataGridMassActionsClassnames.open} {
45
+ animation: ${slideIn}
46
+ ${({ theme }) => theme.transitions.duration.standard}ms
47
+ ${({ theme }) => theme.transitions.easing.easeOut} forwards;
48
+ }
49
+ `;
@@ -0,0 +1,23 @@
1
+ import { type ReactElement, type ReactNode } from 'react';
2
+ import { type DataGridColumns } from '../../DataGrid/types';
3
+ type DataItem = {
4
+ id: string;
5
+ name: string;
6
+ };
7
+ type GridRenderProps = {
8
+ rows: DataItem[];
9
+ columns: DataGridColumns<DataItem>[];
10
+ selectedRows: DataItem[];
11
+ onSelectRow: (rows: DataItem[]) => void;
12
+ massActions: ReactNode;
13
+ onRetry: () => void;
14
+ };
15
+ type TestDataGridMassActionsParams = {
16
+ render: (ui: ReactElement) => void;
17
+ renderGrid: (props: GridRenderProps) => ReactNode;
18
+ };
19
+ /**
20
+ * Общие тесты связки таблицы и DataGridMassActions для DataGrid и DataGridInfinite
21
+ */
22
+ export declare const testDataGridMassActions: ({ render, renderGrid, }: TestDataGridMassActionsParams) => void;
23
+ export {};
@@ -0,0 +1,77 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { screen, setWindowWidth, userEvents, viewportSize, waitForElementToBeRemoved, } from '@ui/dev/tests';
3
+ import { useState } from 'react';
4
+ import { beforeEach, expect, it } from 'vitest';
5
+ import { DataGridMassActions } from '../DataGridMassActions';
6
+ const TEST_ROWS = [
7
+ { id: '1', name: 'Договор поставки' },
8
+ { id: '2', name: 'Счёт-фактура' },
9
+ ];
10
+ const TEST_COLUMNS = [
11
+ {
12
+ field: 'name',
13
+ label: 'Наименование',
14
+ },
15
+ ];
16
+ /**
17
+ * Общие тесты связки таблицы и DataGridMassActions для DataGrid и DataGridInfinite
18
+ */
19
+ export const testDataGridMassActions = ({ render, renderGrid, }) => {
20
+ const TestComponent = ({ initialSelectedRows, }) => {
21
+ const [selectedRows, setSelectedRows] = useState(initialSelectedRows);
22
+ return renderGrid({
23
+ rows: TEST_ROWS,
24
+ columns: TEST_COLUMNS,
25
+ selectedRows,
26
+ onSelectRow: setSelectedRows,
27
+ massActions: (_jsx(DataGridMassActions, { entityDeclension: ['документ', 'документа', 'документов'], main: [{ text: 'Отправить' }] })),
28
+ onRetry: () => { },
29
+ });
30
+ };
31
+ const setup = ({ initialSelectedRows = [], } = {}) => {
32
+ render(_jsx(TestComponent, { initialSelectedRows: initialSelectedRows }));
33
+ };
34
+ beforeEach(() => {
35
+ setWindowWidth(viewportSize.desktop.default.width);
36
+ });
37
+ it('Панель не отображается при пустом выборе', () => {
38
+ setup();
39
+ const closeButton = screen.queryByRole('button', {
40
+ name: 'Снять выделение',
41
+ });
42
+ expect(closeButton).toBeNull();
43
+ });
44
+ it('Панель отображается при выборе строки', async () => {
45
+ setup();
46
+ // 0 - "Выбрать все", 1 - первая строка
47
+ await userEvents.click(screen.getAllByRole('checkbox')[1]);
48
+ expect(screen.getByText('Выбрано: 1 документ')).toBeVisible();
49
+ });
50
+ it('Панель остаётся открытой после нажатия на действие, если выбор не очищен', async () => {
51
+ setup({ initialSelectedRows: TEST_ROWS });
52
+ await userEvents.click(screen.getByRole('button', { name: 'Отправить' }));
53
+ expect(screen.getByText('Выбрано: 2 документа')).toBeVisible();
54
+ });
55
+ it('Выбор строк снимается при закрытии панели', async () => {
56
+ setup({ initialSelectedRows: TEST_ROWS });
57
+ await userEvents.click(screen.getByRole('button', { name: 'Снять выделение' }));
58
+ const checkedCheckboxes = screen.queryAllByRole('checkbox', {
59
+ checked: true,
60
+ });
61
+ expect(checkedCheckboxes).toHaveLength(0);
62
+ });
63
+ it('Панель скрывается при закрытии', async () => {
64
+ setup({ initialSelectedRows: TEST_ROWS });
65
+ await userEvents.click(screen.getByRole('button', { name: 'Снять выделение' }));
66
+ // Панель размонтируется после анимации скрытия
67
+ await expect(waitForElementToBeRemoved(() => screen.queryByRole('button', { name: 'Снять выделение' }))).resolves.toBeUndefined();
68
+ });
69
+ it('Панель не отображается на мобильных разрешениях', () => {
70
+ setWindowWidth(viewportSize.mobile.default.width);
71
+ setup({ initialSelectedRows: TEST_ROWS });
72
+ const closeButton = screen.queryByRole('button', {
73
+ name: 'Снять выделение',
74
+ });
75
+ expect(closeButton).toBeNull();
76
+ });
77
+ };
@@ -0,0 +1,2 @@
1
+ import { type MassActionsProps } from '../MassActions';
2
+ export type DataGridMassActionsProps = Omit<MassActionsProps, 'selectedCount' | 'onClose'>;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export * from './useLogic';
@@ -0,0 +1 @@
1
+ export * from './useLogic';
@@ -0,0 +1,6 @@
1
+ export declare const useLogic: () => {
2
+ isOpen: boolean;
3
+ isMounted: boolean;
4
+ selectedCount: number;
5
+ handleClose: () => void | undefined;
6
+ };
@@ -0,0 +1,21 @@
1
+ import { useContext, useState } from 'react';
2
+ import { DataGridSelectionContext } from '../../DataGrid/DataGridSelectionContext';
3
+ import { useTheme } from '../../theme/hooks';
4
+ import { useDelayedUnmount } from '../../useDelayedUnmount';
5
+ export const useLogic = () => {
6
+ const { selectedRows, onSelectRow, isMassActionsOpen: isOpen, } = useContext(DataGridSelectionContext);
7
+ const { transitions: { duration: { leavingScreen }, }, } = useTheme();
8
+ const { isMounted } = useDelayedUnmount({ isOpen, duration: leavingScreen });
9
+ // Во время анимации скрытия выбор уже пустой — показываем последнее количество выбранных строк
10
+ const [lastSelectedCount, setLastSelectedCount] = useState(selectedRows.length);
11
+ if (selectedRows.length > 0 && selectedRows.length !== lastSelectedCount) {
12
+ setLastSelectedCount(selectedRows.length);
13
+ }
14
+ const handleClose = () => onSelectRow?.([]);
15
+ return {
16
+ isOpen,
17
+ isMounted,
18
+ selectedCount: lastSelectedCount,
19
+ handleClose,
20
+ };
21
+ };
@@ -1,9 +1,14 @@
1
1
  import { type ReactNode } from 'react';
2
2
  import { type IconButtonProps } from '../IconButton';
3
+ import { type MenuProps } from '../Menu';
3
4
  export type IconDropdownButtonProps = IconButtonProps & {
4
5
  /**
5
6
  * Иконка кнопки
6
7
  */
7
8
  icon: ReactNode;
9
+ /**
10
+ * Пропсы выпадающего меню
11
+ */
12
+ popoverProps?: Omit<MenuProps, 'open' | 'anchorEl' | 'title' | 'children'>;
8
13
  };
9
14
  export declare const IconDropdownButton: import("react").ForwardRefExoticComponent<Omit<IconDropdownButtonProps, "ref"> & import("react").RefAttributes<HTMLButtonElement>>;
@@ -4,7 +4,7 @@ import { forwardRef } from 'react';
4
4
  import { IconButton } from '../IconButton';
5
5
  import { Menu } from '../Menu';
6
6
  import { useMenu } from '../useMenu';
7
- export const IconDropdownButton = forwardRef(({ children, icon, title, ...props }, ref) => {
7
+ export const IconDropdownButton = forwardRef(({ children, icon, title, popoverProps, ...props }, ref) => {
8
8
  const { open, anchorRef, handleOpenMenu, handleCloseMenu } = useMenu(ref);
9
- return (_jsxs(_Fragment, { children: [_jsx(ClickAwayListener, { onClickAway: handleCloseMenu, mouseEvent: "onMouseUp", children: _jsx(IconButton, { ...props, ref: anchorRef, onClick: handleOpenMenu, selected: open, title: title, children: icon }) }), _jsx(Menu, { title: title, open: open, anchorEl: anchorRef.current, children: children })] }));
9
+ return (_jsxs(_Fragment, { children: [_jsx(ClickAwayListener, { onClickAway: handleCloseMenu, mouseEvent: "onMouseUp", children: _jsx(IconButton, { ...props, ref: anchorRef, onClick: handleOpenMenu, selected: open, title: title, children: icon }) }), _jsx(Menu, { ...popoverProps, title: title, open: open, anchorEl: anchorRef.current, children: children })] }));
10
10
  });
@@ -0,0 +1,5 @@
1
+ import { type MassActionsProps } from './types';
2
+ /**
3
+ * Базовый компонент панели массовых действий
4
+ */
5
+ export declare const MassActions: ({ main, secondary, selectedCount, entityDeclension, onClose, className, }: MassActionsProps) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,17 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { CrossOutlineMd } from '../../icons/CrossOutlineMd';
3
+ import { IconButton } from '../IconButton';
4
+ import { Tag } from '../Tag';
5
+ import { SECONDARY_POPOVER_PROPS } from './constants';
6
+ import { ReservedLabel, Selection, SelectionLabel, StyledActionGroup, Wrapper, } from './styles';
7
+ import { useLogic } from './useLogic';
8
+ /**
9
+ * Базовый компонент панели массовых действий
10
+ */
11
+ export const MassActions = ({ main, secondary, selectedCount, entityDeclension, onClose, className, }) => {
12
+ const { selectionLabel, reservedLabel } = useLogic({
13
+ entityDeclension,
14
+ selectedCount,
15
+ });
16
+ return (_jsxs(Wrapper, { className: className, children: [_jsx(StyledActionGroup, { main: main, secondary: secondary, secondaryPopoverProps: SECONDARY_POPOVER_PROPS, size: "large" }), _jsxs(Selection, { children: [_jsxs(SelectionLabel, { children: [_jsx(ReservedLabel, { variant: "text", size: "large", label: reservedLabel, "aria-hidden": true }), _jsx(Tag, { variant: "text", size: "large", label: selectionLabel, "aria-live": "polite" })] }), _jsx(IconButton, { variant: "text", "aria-label": "\u0421\u043D\u044F\u0442\u044C \u0432\u044B\u0434\u0435\u043B\u0435\u043D\u0438\u0435", onClick: onClose, children: _jsx(CrossOutlineMd, {}) })] })] }));
17
+ };
@@ -0,0 +1,6 @@
1
+ import { type IconDropdownButtonProps } from '../IconDropdownButton';
2
+ export declare const SECONDARY_POPOVER_PROPS: IconDropdownButtonProps['popoverProps'];
3
+ /**
4
+ * Количество выбранных строк, под которое заранее резервируется ширина счётчика
5
+ */
6
+ export declare const RESERVED_SELECTED_COUNT = 999;
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Смещение меню вверх от кнопки. Компенсирует margin-top 8px у меню из темы и оставляет отступ 8px до кнопки
3
+ */
4
+ const ANCHOR_VERTICAL_POSITION = -16;
5
+ export const SECONDARY_POPOVER_PROPS = {
6
+ anchorOrigin: { vertical: ANCHOR_VERTICAL_POSITION, horizontal: 'left' },
7
+ transformOrigin: { vertical: 'bottom', horizontal: 'left' },
8
+ };
9
+ /**
10
+ * Количество выбранных строк, под которое заранее резервируется ширина счётчика
11
+ */
12
+ export const RESERVED_SELECTED_COUNT = 999;
@@ -0,0 +1,2 @@
1
+ export { MassActions } from './MassActions';
2
+ export type { MassActionsProps } from './types';
@@ -0,0 +1 @@
1
+ export { MassActions } from './MassActions';
@@ -0,0 +1,34 @@
1
+ /// <reference types="react" />
2
+ export declare const Wrapper: import("../styled").StyledComponent<{
3
+ theme?: import("@emotion/react").Theme | undefined;
4
+ as?: import("react").ElementType<any, keyof import("react").JSX.IntrinsicElements> | undefined;
5
+ }, import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, {}>;
6
+ export declare const Selection: import("../styled").StyledComponent<{
7
+ theme?: import("@emotion/react").Theme | undefined;
8
+ as?: import("react").ElementType<any, keyof import("react").JSX.IntrinsicElements> | undefined;
9
+ }, import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, {}>;
10
+ export declare const StyledActionGroup: import("../styled").StyledComponent<import("../ActionGroup").ActionGroupProps<import("react").ElementType, import("react").ElementType> & {
11
+ theme?: import("@emotion/react").Theme | undefined;
12
+ }, {}, {}>;
13
+ /**
14
+ * Невидимая заглушка и счётчик лежат в одной ячейке: ширину задаёт заглушка, поэтому панель не прыгает при смене количества
15
+ */
16
+ export declare const SelectionLabel: import("../styled").StyledComponent<{
17
+ theme?: import("@emotion/react").Theme | undefined;
18
+ as?: import("react").ElementType<any, keyof import("react").JSX.IntrinsicElements> | undefined;
19
+ }, import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, {}>;
20
+ export declare const ReservedLabel: import("../styled").StyledComponent<{
21
+ disabled?: boolean | undefined;
22
+ label?: import("react").ReactNode;
23
+ icon?: import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | undefined;
24
+ className?: string | undefined;
25
+ onDelete?: ((event: import("react").SyntheticEvent<Element, Event>) => void) | undefined;
26
+ color?: import("../Tag").TagColor | undefined;
27
+ variant?: import("../Tag").TagVariant | undefined;
28
+ size?: import("../Tag").TagSize | undefined;
29
+ startAddon?: import("../Tag").TagAddon | undefined;
30
+ endAddon?: import("../Tag").TagAddon | undefined;
31
+ style?: import("react").CSSProperties | undefined;
32
+ } & import("react").HTMLAttributes<HTMLDivElement> & import("react").RefAttributes<HTMLDivElement> & {
33
+ theme?: import("@emotion/react").Theme | undefined;
34
+ }, {}, {}>;
@@ -0,0 +1,43 @@
1
+ import { ActionGroup } from '../ActionGroup';
2
+ import { styled } from '../styled';
3
+ import { Tag } from '../Tag';
4
+ export const Wrapper = styled.div `
5
+ display: flex;
6
+ gap: ${({ theme }) => theme.spacing(3)};
7
+ align-items: center;
8
+
9
+ width: fit-content;
10
+ padding: ${({ theme }) => theme.spacing(4)};
11
+
12
+ background-color: ${({ theme }) => theme.palette.background.default};
13
+
14
+ /* 16px по макету, токена под это значение в теме нет */
15
+ border-radius: calc(${({ theme }) => theme.shape.medium} * 2);
16
+ box-shadow: ${({ theme }) => theme.elevation[200]};
17
+ `;
18
+ export const Selection = styled.div `
19
+ display: flex;
20
+ gap: ${({ theme }) => theme.spacing(2)};
21
+ align-items: center;
22
+
23
+ padding-left: ${({ theme }) => theme.spacing(2)};
24
+ `;
25
+ export const StyledActionGroup = styled(ActionGroup) `
26
+ gap: ${({ theme }) => theme.spacing(3)};
27
+ `;
28
+ /**
29
+ * Невидимая заглушка и счётчик лежат в одной ячейке: ширину задаёт заглушка, поэтому панель не прыгает при смене количества
30
+ */
31
+ export const SelectionLabel = styled.div `
32
+ display: grid;
33
+ justify-items: start;
34
+
35
+ font-variant-numeric: tabular-nums;
36
+
37
+ & > * {
38
+ grid-area: 1 / 1;
39
+ }
40
+ `;
41
+ export const ReservedLabel = styled(Tag) `
42
+ visibility: hidden;
43
+ `;
@@ -0,0 +1,20 @@
1
+ import { type ActionGroupProps } from '../ActionGroup';
2
+ export type MassActionsProps = Pick<ActionGroupProps, 'main' | 'secondary'> & {
3
+ /**
4
+ * Количество выбранных элементов
5
+ */
6
+ selectedCount: number;
7
+ /**
8
+ * Три формы названия выбранной сущности
9
+ * @example ['документ', 'документа', 'документов']
10
+ */
11
+ entityDeclension: [string, string, string];
12
+ /**
13
+ * Обработчик закрытия панели
14
+ */
15
+ onClose: () => void;
16
+ /**
17
+ * Название класса, применяется к корневому компоненту
18
+ */
19
+ className?: string;
20
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export * from './useLogic';
@@ -0,0 +1 @@
1
+ export * from './useLogic';
@@ -0,0 +1,7 @@
1
+ import { type MassActionsProps } from '../types';
2
+ type UseLogicParams = Pick<MassActionsProps, 'entityDeclension' | 'selectedCount'>;
3
+ export declare const useLogic: ({ entityDeclension, selectedCount, }: UseLogicParams) => {
4
+ selectionLabel: string;
5
+ reservedLabel: string;
6
+ };
7
+ export {};
@@ -0,0 +1,9 @@
1
+ import { declensionOfWords } from '@astral/utils/declension/declensionOfWords';
2
+ import { RESERVED_SELECTED_COUNT } from '../constants';
3
+ export const useLogic = ({ entityDeclension, selectedCount, }) => {
4
+ const selectionLabel = `Выбрано: ${selectedCount} ${declensionOfWords(entityDeclension)(selectedCount)}`;
5
+ // Самая длинная форма слова — чтобы ширина панели не менялась при смене склонения
6
+ const longestEntity = entityDeclension.reduce((longest, entity) => entity.length > longest.length ? entity : longest);
7
+ const reservedLabel = `Выбрано: ${RESERVED_SELECTED_COUNT} ${longestEntity}`;
8
+ return { selectionLabel, reservedLabel };
9
+ };
@@ -9,4 +9,4 @@ export declare const StyledMobileButton: import("../../../styled").StyledCompone
9
9
  } & Omit<import("react").DetailedHTMLProps<import("react").ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, ""> & import("react").RefAttributes<HTMLButtonElement> & {
10
10
  theme?: import("@emotion/react").Theme | undefined;
11
11
  }, {}, {}>;
12
- export declare const StyledSecondaryActions: <TSecondaryActionComponent extends import("react").ElementType = import("react").ElementType>({ actions, isDisabled, className, size, }: import("../../../ActionGroup").SecondaryActionProps<TSecondaryActionComponent>) => import("react/jsx-runtime").JSX.Element | null;
12
+ export declare const StyledSecondaryActions: <TSecondaryActionComponent extends import("react").ElementType = import("react").ElementType>({ actions, isDisabled, className, size, popoverProps, }: import("../../../ActionGroup").SecondaryActionProps<TSecondaryActionComponent>) => import("react/jsx-runtime").JSX.Element | null;
@@ -1,3 +1,2 @@
1
- export * from './useDelayedUnmount';
2
1
  export * from './usePanelHeader';
3
2
  export * from './useRegisterClose';
@@ -1,3 +1,2 @@
1
- export * from './useDelayedUnmount';
2
1
  export * from './usePanelHeader';
3
2
  export * from './useRegisterClose';
@@ -1,7 +1,8 @@
1
1
  import { useContext } from 'react';
2
2
  import { useTheme } from '../../../../../theme/hooks';
3
+ import { useDelayedUnmount } from '../../../../../useDelayedUnmount';
3
4
  import { MobileSlidePanelContext } from '../../MobileSlidePanelContext';
4
- import { useDelayedUnmount, usePanelHeader, useRegisterClose } from './hooks';
5
+ import { usePanelHeader, useRegisterClose } from './hooks';
5
6
  /**
6
7
  * Собирает жизненный цикл панели: присутствие в DOM, закрытие и шапку стека.
7
8
  */