kelt-ui-kit-react 2.0.6 → 2.0.9

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 (313) hide show
  1. package/.github/workflows/publish.yml +40 -31
  2. package/README.md +124 -72
  3. package/dist/App.d.ts +2 -3
  4. package/dist/App.menu.d.ts +10 -10
  5. package/dist/App.routes.d.ts +1 -1
  6. package/dist/_core/hooks/HistoryProvider.d.ts +5 -5
  7. package/dist/_core/hooks/ImageChecker.d.ts +5 -6
  8. package/dist/_core/hooks/imageExist.d.ts +5 -6
  9. package/dist/_core/hooks/isEAN13.d.ts +1 -1
  10. package/dist/_core/hooks/objectToQueryParams.d.ts +1 -1
  11. package/dist/_core/hooks/useCurrency.d.ts +3 -3
  12. package/dist/_core/hooks/useDateutils.d.ts +4 -4
  13. package/dist/_core/hooks/useIsMobile.d.ts +1 -1
  14. package/dist/_core/hooks/useToken.d.ts +3 -3
  15. package/dist/_core/hooks/useValidate.d.ts +5 -5
  16. package/dist/_core/hooks/widgetSupport.d.ts +1 -1
  17. package/dist/_core/type/generiqueType.d.ts +4 -0
  18. package/dist/action/Action.view.d.ts +1 -1
  19. package/dist/action/ButtonAction.d.ts +8 -9
  20. package/dist/addressSearch/AddressSearch.d.ts +18 -19
  21. package/dist/addressSearch/AddressSearch.view.d.ts +1 -1
  22. package/dist/badge/Badge.d.ts +10 -9
  23. package/dist/badge/Badge.view.d.ts +1 -1
  24. package/dist/button/Button.d.ts +1 -2
  25. package/dist/button/Button.view.d.ts +1 -1
  26. package/dist/button/button.interface.d.ts +19 -20
  27. package/dist/button/buttonActions/ButtonActions.d.ts +11 -12
  28. package/dist/button/buttonFile/ButtonFile.d.ts +9 -9
  29. package/dist/button/buttonFile/ButtonFile.view.d.ts +1 -1
  30. package/dist/button/buttonFilters/ButtonFilters.d.ts +16 -17
  31. package/dist/card/Card.d.ts +14 -15
  32. package/dist/card/Card.view.d.ts +1 -1
  33. package/dist/card/card.interface.d.ts +10 -10
  34. package/dist/card/cardAction/CardAction.d.ts +13 -14
  35. package/dist/card/cardAction.interface.d.ts +8 -9
  36. package/dist/card/hook/useCardInteractions.d.ts +8 -8
  37. package/dist/carousel/Carousel.d.ts +14 -15
  38. package/dist/carousel/Carousel.view.d.ts +1 -1
  39. package/dist/damier/Damier.d.ts +9 -10
  40. package/dist/damier/Damier.view.d.ts +1 -1
  41. package/dist/damier/damierCell/DamierCell.d.ts +6 -7
  42. package/dist/damier/damierCell/damierCell.interface.d.ts +6 -6
  43. package/dist/dataTable/DataTable.d.ts +37 -33
  44. package/dist/dataTable/DataTable.view.d.ts +1 -1
  45. package/dist/dataTable/dataTable.interface.d.ts +70 -47
  46. package/dist/dataTable/dataTable.utils.d.ts +17 -0
  47. package/dist/dataTable/dataTableActions/DataTableAction.d.ts +9 -10
  48. package/dist/dataTable/dataTableColumn/DataTableColumn.d.ts +14 -8
  49. package/dist/dataTable/dataTableColumn/dataTableColumnFilter/DataTableColumnFilter.d.ts +9 -0
  50. package/dist/dataTable/dataTableColumn/dataTableColumnFilter/dataTableColumnFilterDate/DataTableColumnFilterDate.d.ts +9 -0
  51. package/dist/dataTable/dataTableColumn/dataTableColumnFilter/dataTableColumnFilterDateRange/dataTableColumnFilterDateRange.d.ts +9 -0
  52. package/dist/dataTable/dataTableColumn/dataTableColumnFilter/dataTableColumnFilterEnum/DataTableColumnFilterEnum.d.ts +12 -0
  53. package/dist/dataTable/dataTableColumn/dataTableColumnFilter/dataTableColumnFilterString/DataTableColumnFilterString.d.ts +9 -0
  54. package/dist/dataTable/dataTableColumn/dataTableColumnSort/DataTableColumnSort.d.ts +10 -0
  55. package/dist/dataTable/dataTableFilters/DataTableFilters.d.ts +6 -0
  56. package/dist/dataTable/dataTableLoading/DataTableLoading.d.ts +8 -7
  57. package/dist/dataTable/dataTableRow/DataTableRow.d.ts +12 -11
  58. package/dist/dataTable/useDataTableState.d.ts +13 -0
  59. package/dist/datePicker/DatePicker.d.ts +7 -8
  60. package/dist/datePicker/DatePicker.view.d.ts +1 -1
  61. package/dist/docs/ComponentDocs.d.ts +6 -0
  62. package/dist/docs/catalog.d.ts +8 -0
  63. package/dist/docs/documentation.types.d.ts +21 -0
  64. package/dist/expands/Expands.d.ts +11 -12
  65. package/dist/expands/Expands.view.d.ts +1 -1
  66. package/dist/expands/expand/expand.d.ts +10 -11
  67. package/dist/expands/expand/expand.interface.d.ts +9 -9
  68. package/dist/filAriane/FilAriane.d.ts +8 -9
  69. package/dist/filAriane/FilAriane.view.d.ts +1 -1
  70. package/dist/filAriane/filAriane.interface.d.ts +6 -6
  71. package/dist/form/Form.d.ts +35 -36
  72. package/dist/form/Form.view.d.ts +1 -1
  73. package/dist/form/form.enum.d.ts +16 -15
  74. package/dist/form/form.interface.d.ts +53 -54
  75. package/dist/form/input/Input.d.ts +27 -25
  76. package/dist/form/inputNumber/InputNumber.d.ts +21 -22
  77. package/dist/form/inputNumber/InputNumber.view.d.ts +1 -1
  78. package/dist/form/inputPrice/InputPrice.d.ts +14 -15
  79. package/dist/form/inputPrice/InputPrice.view.d.ts +1 -1
  80. package/dist/form/textArea/TextArea.d.ts +14 -15
  81. package/dist/form/textArea/TextArea.view.d.ts +1 -1
  82. package/dist/geocoding/GeocodingService.d.ts +11 -12
  83. package/dist/geocoding/geocoding.interface.d.ts +20 -20
  84. package/dist/grid/Grid.d.ts +9 -10
  85. package/dist/grid/Grid.view.d.ts +1 -1
  86. package/dist/grid/col/Col.d.ts +5 -6
  87. package/dist/grid/col/colStyled/ColStyled.d.ts +10 -11
  88. package/dist/grid/container/Container.d.ts +5 -6
  89. package/dist/grid/grid.interface.d.ts +23 -23
  90. package/dist/grid/row/Row.d.ts +13 -14
  91. package/dist/header/Header.d.ts +1 -2
  92. package/dist/header/Header.view.d.ts +1 -1
  93. package/dist/header/header.interface.d.ts +12 -12
  94. package/dist/home/Home.d.ts +1 -1
  95. package/dist/icon/Icon.d.ts +1 -2
  96. package/dist/icon/Icons.view.d.ts +1 -1
  97. package/dist/icon/icon.interface.d.ts +5 -5
  98. package/dist/icon/iconSize.enum.d.ts +7 -7
  99. package/dist/index.d.ts +56 -54
  100. package/dist/index.html +18 -18
  101. package/dist/index.js +3964 -3470
  102. package/dist/loader/Loader.d.ts +7 -8
  103. package/dist/loader/Loader.view.d.ts +1 -1
  104. package/dist/main.d.ts +0 -1
  105. package/dist/manifest.json +25 -25
  106. package/dist/menus/Menus.d.ts +5 -6
  107. package/dist/menus/Menus.view.d.ts +1 -1
  108. package/dist/menus/menu/Menu.d.ts +7 -8
  109. package/dist/menus/menu/menu.interface.d.ts +10 -10
  110. package/dist/message/Message.d.ts +6 -7
  111. package/dist/message/Message.view.d.ts +1 -1
  112. package/dist/modal/Modal.d.ts +13 -14
  113. package/dist/modal/Modal.view.d.ts +1 -1
  114. package/dist/notFound/NotFound.d.ts +1 -1
  115. package/dist/overlayPanel/OverlayPanel.d.ts +22 -23
  116. package/dist/overlayPanel/OverlayPanel.view.d.ts +1 -1
  117. package/dist/overlayPanel/overlay.context.d.ts +9 -10
  118. package/dist/overlayPanel/overlayPanelStyled/OverlayPanelStyled.d.ts +9 -9
  119. package/dist/pdfViewer/PdfViewer.d.ts +12 -12
  120. package/dist/pdfViewer/PdfViewer.view.d.ts +1 -1
  121. package/dist/quantity/Quantity.d.ts +10 -11
  122. package/dist/robots.txt +3 -3
  123. package/dist/search/Search.d.ts +21 -22
  124. package/dist/search/Search.view.d.ts +1 -1
  125. package/dist/select/Select.d.ts +23 -24
  126. package/dist/select/Select.view.d.ts +1 -1
  127. package/dist/select/selectOption.interface.d.ts +5 -5
  128. package/dist/sidebar/Sidebar.d.ts +12 -13
  129. package/dist/sidebar/Sidebar.view.d.ts +1 -1
  130. package/dist/sidebarData/SidebarData.d.ts +6 -7
  131. package/dist/sidebarData/SidebarData.view.d.ts +1 -1
  132. package/dist/style.css +1 -1
  133. package/dist/tabs/Tabs.d.ts +17 -17
  134. package/dist/tabs/Tabs.view.d.ts +1 -1
  135. package/dist/toaster/Toaster.d.ts +1 -2
  136. package/dist/toaster/Toaster.view.d.ts +1 -1
  137. package/dist/toaster/store/useToasterStore.d.ts +24 -20
  138. package/docs.plugin.ts +223 -0
  139. package/index.html +20 -18
  140. package/package.json +64 -56
  141. package/public/index.html +18 -18
  142. package/public/manifest.json +25 -25
  143. package/public/robots.txt +3 -3
  144. package/src/App.css +178 -11
  145. package/src/App.menu.tsx +277 -257
  146. package/src/App.routes.tsx +21 -16
  147. package/src/App.tsx +31 -30
  148. package/src/_core/hooks/HistoryProvider.tsx +25 -25
  149. package/src/_core/hooks/ImageChecker.tsx +26 -26
  150. package/src/_core/hooks/imageExist.tsx +9 -9
  151. package/src/_core/hooks/isEAN13.tsx +16 -16
  152. package/src/_core/hooks/objectToQueryParams.tsx +32 -32
  153. package/src/_core/hooks/useCurrency.tsx +18 -18
  154. package/src/_core/hooks/useDateutils.ts +64 -64
  155. package/src/_core/hooks/useIsMobile.ts +18 -18
  156. package/src/_core/hooks/useToken.tsx +19 -19
  157. package/src/_core/hooks/useValidate.tsx +18 -18
  158. package/src/_core/hooks/widgetSupport.tsx +39 -39
  159. package/src/_core/type/generiqueType.tsx +4 -0
  160. package/src/action/Action.view.tsx +21 -21
  161. package/src/action/ButtonAction.tsx +32 -32
  162. package/src/action/action.css +20 -20
  163. package/src/addressSearch/AddressSearch.tsx +246 -246
  164. package/src/addressSearch/AddressSearch.view.tsx +52 -52
  165. package/src/addressSearch/addressSearch.css +132 -132
  166. package/src/badge/Badge.tsx +41 -34
  167. package/src/badge/Badge.view.tsx +15 -15
  168. package/src/badge/badge.css +63 -55
  169. package/src/button/Button.tsx +48 -48
  170. package/src/button/Button.view.tsx +85 -85
  171. package/src/button/button.css +53 -53
  172. package/src/button/button.interface.tsx +22 -22
  173. package/src/button/buttonActions/ButtonActions.tsx +104 -104
  174. package/src/button/buttonFile/ButtonFile.tsx +59 -59
  175. package/src/button/buttonFile/ButtonFile.view.tsx +70 -70
  176. package/src/button/buttonFilters/ButtonFilters.tsx +102 -102
  177. package/src/button/buttonFilters/buttonFilters.css +22 -22
  178. package/src/card/Card.tsx +151 -151
  179. package/src/card/Card.view.tsx +73 -73
  180. package/src/card/card.css +144 -144
  181. package/src/card/card.interface.tsx +9 -9
  182. package/src/card/cardAction/CardAction.tsx +135 -135
  183. package/src/card/cardAction/cardAction.css +10 -10
  184. package/src/card/cardAction.interface.tsx +10 -10
  185. package/src/card/hook/useCardInteractions.tsx +30 -30
  186. package/src/carousel/Carousel.css +44 -44
  187. package/src/carousel/Carousel.tsx +115 -115
  188. package/src/carousel/Carousel.view.tsx +13 -13
  189. package/src/damier/Damier.tsx +64 -64
  190. package/src/damier/Damier.view.tsx +31 -31
  191. package/src/damier/damier.css +50 -50
  192. package/src/damier/damierCell/DamierCell.tsx +18 -18
  193. package/src/damier/damierCell/damierCell.interface.tsx +5 -5
  194. package/src/dataTable/DataTable.tsx +840 -746
  195. package/src/dataTable/DataTable.view.tsx +105 -71
  196. package/src/dataTable/dataTable.css +233 -208
  197. package/src/dataTable/dataTable.interface.ts +66 -45
  198. package/src/dataTable/dataTable.utils.ts +45 -0
  199. package/src/dataTable/dataTableActions/DataTableAction.tsx +82 -65
  200. package/src/dataTable/dataTableColumn/DataTableColumn.tsx +48 -27
  201. package/src/dataTable/dataTableColumn/dataTableColumnFilter/DataTableColumnFilter.tsx +36 -0
  202. package/src/dataTable/dataTableColumn/dataTableColumnFilter/dataTableColumnFilter.css +0 -0
  203. package/src/dataTable/dataTableColumn/dataTableColumnFilter/dataTableColumnFilterDate/DataTableColumnFilterDate.tsx +39 -0
  204. package/src/dataTable/dataTableColumn/dataTableColumnFilter/dataTableColumnFilterDateRange/dataTableColumnFilterDateRange.tsx +80 -0
  205. package/src/dataTable/dataTableColumn/dataTableColumnFilter/dataTableColumnFilterEnum/DataTableColumnFilterEnum.tsx +89 -0
  206. package/src/dataTable/dataTableColumn/dataTableColumnFilter/dataTableColumnFilterString/DataTableColumnFilterString.tsx +37 -0
  207. package/src/dataTable/dataTableColumn/dataTableColumnSort/DataTableColumnSort.tsx +42 -0
  208. package/src/dataTable/dataTableFilters/DataTableFilters.tsx +91 -0
  209. package/src/dataTable/dataTableFilters/dataTableFilters.css +16 -0
  210. package/src/dataTable/dataTableLoading/DataTableLoading.tsx +23 -21
  211. package/src/dataTable/dataTableRow/DataTableRow.tsx +155 -208
  212. package/src/dataTable/useDataTableState.tsx +89 -0
  213. package/src/datePicker/DatePicker.tsx +110 -110
  214. package/src/datePicker/DatePicker.view.tsx +9 -9
  215. package/src/datePicker/datePicker.css +77 -77
  216. package/src/docs/ComponentDocs.tsx +156 -0
  217. package/src/docs/catalog.ts +40 -0
  218. package/src/docs/documentation.types.ts +20 -0
  219. package/src/docs/virtual.d.ts +7 -0
  220. package/src/expands/Expands.tsx +46 -46
  221. package/src/expands/Expands.view.tsx +90 -90
  222. package/src/expands/expand/expand.interface.tsx +8 -8
  223. package/src/expands/expands.css +101 -101
  224. package/src/filAriane/FilAriane.tsx +57 -57
  225. package/src/filAriane/FilAriane.view.tsx +28 -28
  226. package/src/filAriane/filAriane.css +23 -23
  227. package/src/filAriane/filAriane.interface.tsx +6 -6
  228. package/src/form/Form.tsx +477 -477
  229. package/src/form/Form.view.tsx +67 -67
  230. package/src/form/form.css +135 -135
  231. package/src/form/form.enum.ts +16 -15
  232. package/src/form/form.interface.tsx +67 -67
  233. package/src/form/input/Input.tsx +146 -106
  234. package/src/form/input/input.css +39 -29
  235. package/src/form/inputNumber/InputNumber.tsx +132 -132
  236. package/src/form/inputNumber/InputNumber.view.tsx +15 -15
  237. package/src/form/inputPrice/InputPrice.tsx +42 -42
  238. package/src/form/inputPrice/InputPrice.view.tsx +15 -15
  239. package/src/form/textArea/TextArea.tsx +59 -59
  240. package/src/form/textArea/TextArea.view.tsx +34 -34
  241. package/src/form/textArea/textArea.css +9 -9
  242. package/src/geocoding/GeocodingService.ts +89 -89
  243. package/src/geocoding/geocoding.interface.ts +25 -25
  244. package/src/grid/Grid.view.tsx +24 -24
  245. package/src/grid/container/Container.tsx +8 -8
  246. package/src/grid/container/container.css +4 -4
  247. package/src/grid/grid.interface.tsx +27 -27
  248. package/src/grid/row/row.css +25 -25
  249. package/src/header/Header.tsx +64 -64
  250. package/src/header/Header.view.tsx +5 -5
  251. package/src/header/header.css +43 -43
  252. package/src/header/header.interface.tsx +11 -11
  253. package/src/home/Home.tsx +40 -3
  254. package/src/icon/Icon.tsx +6 -6
  255. package/src/icon/Icons.view.tsx +29 -29
  256. package/src/icon/icon.css +20 -20
  257. package/src/icon/icon.interface.tsx +7 -6
  258. package/src/icon/iconSize.enum.ts +7 -7
  259. package/src/index.css +547 -547
  260. package/src/index.ts +76 -70
  261. package/src/loader/Loader.tsx +37 -37
  262. package/src/loader/Loader.view.tsx +20 -20
  263. package/src/loader/loader.css +30 -30
  264. package/src/main.tsx +10 -10
  265. package/src/menus/Menus.tsx +43 -43
  266. package/src/menus/Menus.view.tsx +39 -39
  267. package/src/menus/menu/Menu.tsx +18 -18
  268. package/src/menus/menu/menu.interface.tsx +9 -9
  269. package/src/menus/menus.css +47 -47
  270. package/src/message/Message.tsx +30 -30
  271. package/src/message/Message.view.tsx +22 -22
  272. package/src/message/message.css +29 -29
  273. package/src/modal/Modal.tsx +59 -59
  274. package/src/modal/Modal.view.tsx +26 -26
  275. package/src/modal/modal.css +71 -71
  276. package/src/notFound/NotFound.tsx +3 -3
  277. package/src/overlayPanel/OverlayPanel.tsx +260 -255
  278. package/src/overlayPanel/OverlayPanel.view.tsx +25 -25
  279. package/src/overlayPanel/overlay.context.tsx +30 -30
  280. package/src/overlayPanel/overlayPanel.css +43 -43
  281. package/src/overlayPanel/overlayPanelStyled/OverlayPanelStyled.tsx +23 -23
  282. package/src/pdfViewer/PdfViewer.tsx +73 -73
  283. package/src/pdfViewer/PdfViewer.view.tsx +5 -5
  284. package/src/quantity/Quantity.tsx +103 -103
  285. package/src/quantity/quantity.css +26 -26
  286. package/src/search/Search.tsx +161 -161
  287. package/src/search/Search.view.tsx +14 -14
  288. package/src/search/search.css +59 -59
  289. package/src/select/Select.tsx +245 -245
  290. package/src/select/Select.view.tsx +71 -71
  291. package/src/select/select.css +150 -150
  292. package/src/select/selectOption.interface.ts +5 -5
  293. package/src/sidebar/Sidebar.tsx +213 -213
  294. package/src/sidebar/Sidebar.view.tsx +17 -17
  295. package/src/sidebar/sidebar.css +103 -103
  296. package/src/sidebarData/SidebarData.tsx +19 -19
  297. package/src/sidebarData/SidebarData.view.tsx +19 -19
  298. package/src/sidebarData/sidebarData.css +27 -27
  299. package/src/tabs/Tabs.tsx +101 -90
  300. package/src/tabs/Tabs.view.tsx +20 -20
  301. package/src/tabs/tabs.css +29 -29
  302. package/src/toaster/Toaster.tsx +61 -60
  303. package/src/toaster/Toaster.view.tsx +25 -25
  304. package/src/toaster/store/useToasterStore.tsx +54 -43
  305. package/src/toaster/toaster.css +105 -92
  306. package/tests/dataTable.test.mjs +93 -0
  307. package/tests/documentation.test.mjs +75 -0
  308. package/tsconfig.docs.json +10 -0
  309. package/tsconfig.json +28 -28
  310. package/vite.config.ts +26 -20
  311. package/vite.config.ts.timestamp-1733262892554-a13dfef6e8a29.mjs +24 -24
  312. package/vite.config.ts.timestamp-1757847121745-e5a5bf54b81b.mjs +28 -28
  313. package/vite.config.ts.timestamp-1759742478362-4ac5c5a7db34b.mjs +28 -28
@@ -1,746 +1,840 @@
1
- import { useCallback, useEffect, useRef, useState } from "react";
2
- import { createPortal } from "react-dom";
3
- import { useIsMobile } from "../_core/hooks/useIsMobile";
4
- import { Card } from "../card/Card";
5
- import { TypeInputEnum } from "../form/form.enum";
6
- import { Input } from "../form/input/Input";
7
- import { Icon } from "../icon/Icon";
8
- import { Loader } from "../loader/Loader";
9
- import "./dataTable.css";
10
- import {
11
- dataTableActionsInterface,
12
- dataTableColumnsInterface,
13
- } from "./dataTable.interface";
14
- import { DataTableAction } from "./dataTableActions/DataTableAction";
15
- import { DataTableColumn } from "./dataTableColumn/DataTableColumn";
16
- import { DataTableLoading } from "./dataTableLoading/DataTableLoading";
17
- import { DataTableRow } from "./dataTableRow/DataTableRow";
18
- export type DataTableProps<T> = {
19
- id: string;
20
- name?: string;
21
- className?: string;
22
- children?: JSX.Element;
23
- element?: JSX.Element;
24
- data: T[];
25
- emptyMessage?: string;
26
- actionsColumnWidth?: number;
27
- actions?: dataTableActionsInterface<T>[];
28
- footerAction?: (props: {
29
- close: () => void;
30
- selectedRows: T[];
31
- }) => JSX.Element;
32
- columns: dataTableColumnsInterface<T>[];
33
- loading?: boolean;
34
- onRowClick?: (row: any) => void;
35
- onColumnClick?: (column: any) => void;
36
- onLoadMore?: () => void;
37
- hasMore?: boolean; // pour savoir s'il reste des données à charger
38
- onSelectionChange?: (selectedRows: T[]) => void;
39
- autoFocusFirstEditable?: boolean;
40
- hideToggleAll?: boolean;
41
- limitSelect?: number;
42
- getRowId?: (row: T) => string | number;
43
- onRowStyle?: (row: T) => React.CSSProperties | undefined;
44
- onRowClassName?: (row: T) => string | undefined;
45
- rowStyle?: (row: T) => React.CSSProperties | undefined;
46
- rowClassName?: (row: T) => string | undefined;
47
- mobileCardView?: boolean;
48
- };
49
- export function DataTable<T>(props: DataTableProps<T>) {
50
- const {
51
- id,
52
- name,
53
- className,
54
- children,
55
- element,
56
- data = [],
57
- actionsColumnWidth = 200,
58
- actions = [],
59
- footerAction,
60
- columns = [],
61
- loading,
62
- onRowClick,
63
- onColumnClick,
64
- onLoadMore,
65
- emptyMessage,
66
- hasMore,
67
- onSelectionChange,
68
- autoFocusFirstEditable = false,
69
- hideToggleAll = false,
70
- limitSelect = undefined,
71
- getRowId,
72
- onRowStyle,
73
- onRowClassName,
74
- rowStyle,
75
- rowClassName,
76
- mobileCardView = false,
77
- } = props;
78
-
79
- const isMobile = useIsMobile();
80
- const showMobileCardView = mobileCardView && isMobile;
81
- const loaderRef = useRef<HTMLDivElement | null>(null);
82
- const footerActionRef = useRef<HTMLDivElement | null>(null);
83
- const hasAutoFocusedRef = useRef(false);
84
- const [isFooterActionVisible, setIsFooterActionVisible] = useState(false);
85
- const [footerActionOffset, setFooterActionOffset] = useState(0);
86
- const [selectedRowIds, setSelectedRowIds] = useState<Set<string | number>>(
87
- new Set(),
88
- );
89
-
90
- const focusEditableElement = useCallback((element: HTMLElement | null) => {
91
- if (!element) {
92
- return;
93
- }
94
-
95
- element.scrollIntoView({ block: "nearest", inline: "nearest" });
96
-
97
- requestAnimationFrame(() => {
98
- element.focus();
99
-
100
- if (
101
- element instanceof HTMLInputElement ||
102
- element instanceof HTMLTextAreaElement
103
- ) {
104
- element.select();
105
- }
106
- });
107
- }, []);
108
-
109
- const isInteractiveClick = useCallback((target: EventTarget | null) => {
110
- const element =
111
- target instanceof Element
112
- ? target
113
- : target instanceof Node
114
- ? target.parentElement
115
- : null;
116
-
117
- if (!element) {
118
- return false;
119
- }
120
-
121
- return Boolean(
122
- element.closest(
123
- "input,button,a,select,textarea,label,option,[role='button'],[role='checkbox'],[role='option'],[contenteditable='true'],[data-stop-row-click='true']",
124
- ),
125
- );
126
- }, []);
127
-
128
- const toggleSelectAll = useCallback(() => {
129
- if (selectedRowIds.size === data.length) {
130
- setSelectedRowIds(new Set());
131
- onSelectionChange?.([]);
132
- setIsFooterActionVisible(false);
133
- } else {
134
- let allIds = data.map((d) =>
135
- getRowId ? getRowId(d) : ((d as any).id ?? d),
136
- );
137
-
138
- if (limitSelect && allIds.length > limitSelect) {
139
- allIds = allIds.slice(0, limitSelect);
140
- }
141
-
142
- const newSelection = new Set(allIds);
143
- setSelectedRowIds(newSelection);
144
- setIsFooterActionVisible(true);
145
- onSelectionChange?.(
146
- data.filter((d) =>
147
- newSelection.has(getRowId ? getRowId(d) : ((d as any).id ?? d)),
148
- ),
149
- );
150
- }
151
- }, [data, onSelectionChange, limitSelect, getRowId, selectedRowIds.size]);
152
-
153
- const focusNextColumn = useCallback(
154
- (columnIndex: number, rowIndex: number) => {
155
- if (typeof document === "undefined") {
156
- return;
157
- }
158
-
159
- const table =
160
- document.getElementById(`${id}-mobile`) ??
161
- document.getElementById(id);
162
- const eligibleColumnIndexes = columns.reduce<number[]>(
163
- (indexes, column, idx) => {
164
- if (
165
- column.editable &&
166
- column.enterToNextColumn &&
167
- column.type !== "select" &&
168
- column.type !== "image"
169
- ) {
170
- indexes.push(idx);
171
- }
172
-
173
- return indexes;
174
- },
175
- [],
176
- );
177
-
178
- if (!table || eligibleColumnIndexes.length === 0) {
179
- return;
180
- }
181
-
182
- const currentEligibleIndex = eligibleColumnIndexes.indexOf(columnIndex);
183
-
184
- if (currentEligibleIndex === -1) {
185
- return;
186
- }
187
-
188
- const editableCells = data.flatMap((_, currentRowIndex) =>
189
- eligibleColumnIndexes.map((currentColumnIndex) => ({
190
- rowIndex: currentRowIndex,
191
- columnIndex: currentColumnIndex,
192
- })),
193
- );
194
-
195
- const currentCellIndex = editableCells.findIndex(
196
- (cell) =>
197
- cell.rowIndex === rowIndex && cell.columnIndex === columnIndex,
198
- );
199
-
200
- if (currentCellIndex === -1) {
201
- return;
202
- }
203
-
204
- const focusableSelector =
205
- 'input:not([disabled]), textarea:not([disabled]), [contenteditable="true"]';
206
- let nextInput: HTMLElement | null = null;
207
-
208
- for (let offset = 1; offset <= editableCells.length; offset += 1) {
209
- const nextCell =
210
- editableCells[(currentCellIndex + offset) % editableCells.length];
211
- const cell = table.querySelector(
212
- `tbody td[data-row-index="${nextCell.rowIndex}"][data-column-index="${nextCell.columnIndex}"]`,
213
- );
214
-
215
- if (!(cell instanceof HTMLElement)) {
216
- continue;
217
- }
218
-
219
- const focusableElement =
220
- cell.querySelector<HTMLElement>(focusableSelector);
221
-
222
- if (focusableElement) {
223
- nextInput = focusableElement;
224
- break;
225
- }
226
- }
227
-
228
- if (!nextInput) {
229
- return;
230
- }
231
-
232
- focusEditableElement(nextInput);
233
- },
234
- [columns, data, focusEditableElement, id],
235
- );
236
-
237
- const focusNextRow = useCallback(
238
- (rowIndex: number) => {
239
- if (typeof document === "undefined") {
240
- return;
241
- }
242
-
243
- const table =
244
- document.getElementById(`${id}-mobile`) ?? document.getElementById(id);
245
- if (!table) {
246
- return;
247
- }
248
-
249
- const eligibleColumnIndexes = columns.reduce<number[]>(
250
- (indexes, column, idx) => {
251
- if (
252
- column.editable &&
253
- column.type !== "select" &&
254
- column.type !== "image"
255
- ) {
256
- indexes.push(idx);
257
- }
258
-
259
- return indexes;
260
- },
261
- [],
262
- );
263
-
264
- if (eligibleColumnIndexes.length === 0) {
265
- return;
266
- }
267
-
268
- const firstEligibleColumnIndex = eligibleColumnIndexes[0];
269
- const focusableSelector =
270
- 'input:not([disabled]), textarea:not([disabled]), [contenteditable="true"]';
271
- let nextInput: HTMLElement | null = null;
272
-
273
- for (let offset = 1; offset <= data.length; offset += 1) {
274
- const nextRowIndex = (rowIndex + offset) % data.length;
275
- const cell = table.querySelector(
276
- `[data-row-index="${nextRowIndex}"][data-column-index="${firstEligibleColumnIndex}"]`,
277
- );
278
-
279
- if (!(cell instanceof HTMLElement)) {
280
- continue;
281
- }
282
-
283
- const focusableElement =
284
- cell.querySelector<HTMLElement>(focusableSelector);
285
-
286
- if (focusableElement) {
287
- nextInput = focusableElement;
288
- break;
289
- }
290
- }
291
-
292
- if (!nextInput) {
293
- return;
294
- }
295
-
296
- focusEditableElement(nextInput);
297
- },
298
- [columns, data.length, focusEditableElement, id],
299
- );
300
-
301
- const handleClickRow = useCallback(
302
- (e: React.MouseEvent, row: T) => {
303
- e.stopPropagation();
304
-
305
- if (isInteractiveClick(e.target)) {
306
- return;
307
- }
308
-
309
- const selectedText =
310
- typeof window !== "undefined"
311
- ? window.getSelection?.()?.toString().trim()
312
- : "";
313
- if (selectedText) {
314
- return;
315
- }
316
-
317
- if (onRowClick) {
318
- onRowClick?.(row);
319
- }
320
- },
321
- [isInteractiveClick, onRowClick],
322
- );
323
- const toggleRow = useCallback(
324
- (e: React.ChangeEvent<HTMLInputElement>, row: T) => {
325
- e.stopPropagation();
326
- const rowId = getRowId ? getRowId(row) : ((row as any).id ?? row);
327
- const newSelection = new Set(selectedRowIds);
328
-
329
- if (newSelection.has(rowId)) {
330
- newSelection.delete(rowId);
331
- } else {
332
- if (limitSelect && newSelection.size >= limitSelect) {
333
- // 🚫 limite atteinte
334
- return;
335
- }
336
- newSelection.add(rowId);
337
- }
338
-
339
- setSelectedRowIds(newSelection);
340
- setIsFooterActionVisible(newSelection.size > 0);
341
- const selectedItems = data.filter((d) =>
342
- newSelection.has(getRowId ? getRowId(d) : ((d as any).id ?? d)),
343
- );
344
- onSelectionChange?.(selectedItems);
345
- },
346
- [data, getRowId, limitSelect, onSelectionChange, selectedRowIds],
347
- );
348
- const showFooterAction = Boolean(footerAction && isFooterActionVisible);
349
- const closeFooterAction = useCallback(() => {
350
- setIsFooterActionVisible(false);
351
- }, []);
352
- const selectedRows = data.filter((d) =>
353
- selectedRowIds.has(getRowId ? getRowId(d) : ((d as any).id ?? d)),
354
- );
355
- useEffect(() => {
356
- if (loading || !autoFocusFirstEditable || data.length === 0) {
357
- hasAutoFocusedRef.current = false;
358
- return;
359
- }
360
-
361
- if (hasAutoFocusedRef.current) {
362
- return;
363
- }
364
-
365
- const firstEditableColumnIndex = columns.findIndex(
366
- (column) =>
367
- column.editable &&
368
- column.enterToNextColumn &&
369
- column.type !== "select" &&
370
- column.type !== "image",
371
- );
372
-
373
- if (firstEditableColumnIndex === -1) {
374
- return;
375
- }
376
-
377
- const table =
378
- document.getElementById(`${id}-mobile`) ?? document.getElementById(id);
379
- const firstCell = table?.querySelector(
380
- `[data-row-index="0"][data-column-index="${firstEditableColumnIndex}"]`,
381
- );
382
-
383
- if (!(firstCell instanceof HTMLElement)) {
384
- return;
385
- }
386
-
387
- const firstInput = firstCell.querySelector<HTMLElement>(
388
- 'input:not([disabled]), textarea:not([disabled]), [contenteditable="true"]',
389
- );
390
-
391
- if (!firstInput) {
392
- return;
393
- }
394
-
395
- hasAutoFocusedRef.current = true;
396
- focusEditableElement(firstInput);
397
- }, [
398
- autoFocusFirstEditable,
399
- columns,
400
- data.length,
401
- focusEditableElement,
402
- id,
403
- loading,
404
- ]);
405
- useEffect(() => {
406
- if (!showFooterAction) {
407
- setFooterActionOffset(0);
408
- return;
409
- }
410
-
411
- const footerActionElement = footerActionRef.current;
412
-
413
- if (!footerActionElement) {
414
- return;
415
- }
416
-
417
- const updateFooterActionOffset = () => {
418
- const { height } = footerActionElement.getBoundingClientRect();
419
- setFooterActionOffset(Math.ceil(height) + 16);
420
- };
421
-
422
- updateFooterActionOffset();
423
-
424
- if (typeof ResizeObserver === "undefined") {
425
- return;
426
- }
427
-
428
- const resizeObserver = new ResizeObserver(() => {
429
- updateFooterActionOffset();
430
- });
431
-
432
- resizeObserver.observe(footerActionElement);
433
-
434
- return () => {
435
- resizeObserver.disconnect();
436
- };
437
- }, [showFooterAction]);
438
- useEffect(() => {
439
- if (!onLoadMore || !hasMore) return;
440
-
441
- const observer = new IntersectionObserver(
442
- (entries) => {
443
- if (entries[0].isIntersecting) {
444
- onLoadMore();
445
- }
446
- },
447
- {
448
- root: null,
449
- rootMargin: "0px",
450
- threshold: 1.0,
451
- },
452
- );
453
-
454
- if (loaderRef.current) {
455
- observer.observe(loaderRef.current);
456
- }
457
-
458
- return () => {
459
- if (loaderRef.current) {
460
- observer.unobserve(loaderRef.current);
461
- }
462
- };
463
- }, [onLoadMore, hasMore]);
464
- return (
465
- <div
466
- className={`data-table-container ${showFooterAction ? "data-table-container--with-footer-action" : ""}`}
467
- style={
468
- showFooterAction
469
- ? { paddingBottom: `${footerActionOffset}px` }
470
- : undefined
471
- }
472
- >
473
- {name && <h2 className="mb-2">{name}</h2>}
474
- {element}
475
- {showMobileCardView && (
476
- <div
477
- id={`${id}-mobile`}
478
- className={`data-table-mobile-cards ${className ? className : ""}`}
479
- >
480
- {loading && <Loader display={loading} />}
481
- {!loading &&
482
- data.map((row, rowIndex) => {
483
- const customStyle = (onRowStyle ?? rowStyle)?.(row);
484
- const customClass =
485
- (onRowClassName ?? rowClassName)?.(row) ?? "";
486
- const rowId = getRowId
487
- ? getRowId(row)
488
- : ((row as any).id ?? row);
489
-
490
- return (
491
- <Card
492
- key={rowId as React.Key}
493
- item={{ title: "" }}
494
- className={`data-table-mobile-card ${customClass}`.trim()}
495
- handleClick={onRowClick ? () => onRowClick(row) : undefined}
496
- >
497
- <div
498
- className="data-table-mobile-card__content"
499
- style={customStyle}
500
- data-row-index={rowIndex}
501
- onMouseDown={(event) => {
502
- if (isInteractiveClick(event.target)) {
503
- event.stopPropagation();
504
- }
505
- }}
506
- >
507
- {onSelectionChange && (
508
- <div className="data-table-mobile-card__field data-table-mobile-card__selection">
509
- <span className="data-table-mobile-card__label">
510
- Sélection
511
- </span>
512
- <Input
513
- id={`select-card-${rowIndex}`}
514
- name={`select-card-${rowIndex}`}
515
- type={TypeInputEnum.CHECKBOX}
516
- disabled={
517
- limitSelect !== undefined &&
518
- selectedRowIds.size >= limitSelect &&
519
- !selectedRowIds.has(rowId)
520
- }
521
- checked={selectedRowIds.has(rowId)}
522
- onChange={(event) => toggleRow(event, row)}
523
- />
524
- </div>
525
- )}
526
- {columns.map((column, columnIndex) => (
527
- <div
528
- className="data-table-mobile-card__field"
529
- key={column.id}
530
- >
531
- <span className="data-table-mobile-card__label">
532
- {column.label}
533
- </span>
534
- <DataTableRow
535
- container="card"
536
- column={column}
537
- idx={columnIndex}
538
- row={row}
539
- rowIndex={rowIndex}
540
- onEnterNextColumn={focusNextColumn}
541
- />
542
- </div>
543
- ))}
544
- {actions.length > 0 && (
545
- <div className="data-table-mobile-card__actions card-actions">
546
- {actions.map((action, actionIndex) => (
547
- <DataTableAction
548
- key={action.id}
549
- action={action}
550
- row={row}
551
- rowIndex={rowIndex}
552
- index={actionIndex}
553
- onFocusNextRow={focusNextRow}
554
- />
555
- ))}
556
- </div>
557
- )}
558
- </div>
559
- </Card>
560
- );
561
- })}
562
- {!loading && data.length === 0 && (
563
- <div className="data-table-mobile-cards__empty">
564
- {emptyMessage ?? "Aucune donnée"}
565
- </div>
566
- )}
567
- </div>
568
- )}
569
- <table
570
- className={`data-table ${className ? className : ""}`}
571
- id={id}
572
- style={showMobileCardView ? { display: "none" } : undefined}
573
- >
574
- <thead>
575
- <tr>
576
- {onSelectionChange && (
577
- <th
578
- className={`select-cell ${hideToggleAll ? "hidden" : ""}`}
579
- style={{ width: "20px", textAlign: "center" }}
580
- >
581
- <Input
582
- id={`select-th-${id}`}
583
- name={`select-th-${id}`}
584
- type={TypeInputEnum.CHECKBOX}
585
- checked={
586
- data.length > 0 && selectedRowIds.size === data.length
587
- }
588
- onChange={toggleSelectAll}
589
- />
590
- </th>
591
- )}
592
- {columns.map((column, idx) => (
593
- <DataTableColumn
594
- key={idx}
595
- column={column}
596
- idx={idx}
597
- onColumnClick={onColumnClick}
598
- />
599
- ))}
600
- {actions.length > 0 && (
601
- <th
602
- style={{
603
- width: `${actionsColumnWidth}px`,
604
- minWidth: `${actionsColumnWidth}px`,
605
- }}
606
- align="right"
607
- key={"actions"}
608
- ></th>
609
- )}
610
- </tr>
611
- </thead>
612
- <tbody>
613
- {loading ? (
614
- <tr>
615
- <td
616
- align="center"
617
- colSpan={columns.length + (actions.length > 0 ? 1 : 0)}
618
- >
619
- <div className="mt-3">
620
- <Loader display={loading} />
621
- </div>
622
- </td>
623
- </tr>
624
- ) : (
625
- <></>
626
- )}
627
- {!loading &&
628
- data.map((row, rowIndex) => {
629
- const customStyle = (onRowStyle ?? rowStyle)?.(row);
630
- const customClass = (onRowClassName ?? rowClassName)?.(row) ?? "";
631
-
632
- return (
633
- <tr
634
- key={rowIndex}
635
- className={`${rowIndex % 2 === 0 ? "even" : "odd"} ${customClass}`.trim()}
636
- style={customStyle}
637
- data-row-index={rowIndex}
638
- onClick={(e) => handleClickRow?.(e, row)}
639
- >
640
- {onSelectionChange && (
641
- <td
642
- className="select-cell"
643
- style={{ textAlign: "center" }}
644
- onClick={(e) => {
645
- e.stopPropagation();
646
- setIsFooterActionVisible(true);
647
- }}
648
- >
649
- <Input
650
- id={`select-row-${rowIndex}`}
651
- name={`select-row-${rowIndex}`}
652
- type={TypeInputEnum.CHECKBOX}
653
- disabled={
654
- limitSelect !== undefined &&
655
- selectedRowIds.size >= limitSelect &&
656
- !selectedRowIds.has(
657
- getRowId ? getRowId(row) : ((row as any).id ?? row),
658
- )
659
- }
660
- checked={selectedRowIds.has(
661
- getRowId ? getRowId(row) : ((row as any).id ?? row),
662
- )}
663
- onChange={(e) => toggleRow(e, row)}
664
- />
665
- </td>
666
- )}
667
- {columns.map((column, idx) => (
668
- <DataTableRow
669
- column={column}
670
- idx={idx}
671
- row={row}
672
- rowIndex={rowIndex}
673
- key={idx}
674
- onEnterNextColumn={focusNextColumn}
675
- />
676
- ))}
677
- {actions.length > 0 && (
678
- <td
679
- style={{
680
- width: `${actionsColumnWidth}px`,
681
- minWidth: `${actionsColumnWidth}px`,
682
- }}
683
- align="right"
684
- onClick={(e) => e.stopPropagation()}
685
- >
686
- <div className="d-flex align-items-center justify-content-end">
687
- {actions.map((action, index) => (
688
- <DataTableAction
689
- key={index}
690
- action={action}
691
- row={row}
692
- rowIndex={rowIndex}
693
- index={index}
694
- onFocusNextRow={focusNextRow}
695
- />
696
- ))}
697
- </div>
698
- </td>
699
- )}
700
- </tr>
701
- );
702
- })}
703
- {!loading && data.length === 0 && (
704
- <DataTableLoading
705
- columns={columns}
706
- actions={actions}
707
- emptyMessage={emptyMessage}
708
- />
709
- )}
710
- </tbody>
711
- </table>
712
-
713
- {showFooterAction &&
714
- typeof document !== "undefined" &&
715
- createPortal(
716
- <div
717
- ref={footerActionRef}
718
- className="data-table-footer-action"
719
- data-footer-action="true"
720
- >
721
- <div className="data-table-footer-action__content">
722
- <div
723
- className="data-table-footer-action__close"
724
- aria-label="Fermer les actions"
725
- onClick={closeFooterAction}
726
- >
727
- <Icon classIcon={"bi-x"} />
728
- </div>
729
- {footerAction?.({
730
- close: closeFooterAction,
731
- selectedRows,
732
- })}
733
- </div>
734
- </div>,
735
- document.body,
736
- )}
737
-
738
- {children}
739
- {hasMore && (
740
- <div ref={loaderRef} className="infinite-scroll-loader">
741
- <Loader display={true} />
742
- </div>
743
- )}
744
- </div>
745
- );
746
- }
1
+ import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
2
+ import { createPortal } from "react-dom";
3
+ import { useIsMobile } from "../_core/hooks/useIsMobile";
4
+ import { Card } from "../card/Card";
5
+ import { TypeInputEnum } from "../form/form.enum";
6
+ import { Input } from "../form/input/Input";
7
+ import { Icon } from "../icon/Icon";
8
+ import { Loader } from "../loader/Loader";
9
+ import "./dataTable.css";
10
+ import {
11
+ dataTableActionsInterface,
12
+ dataTableColumnsInterface,
13
+ } from "./dataTable.interface";
14
+ import { DataTableAction } from "./dataTableActions/DataTableAction";
15
+ import { DataTableColumn } from "./dataTableColumn/DataTableColumn";
16
+ import { DataTableLoading } from "./dataTableLoading/DataTableLoading";
17
+ import { DataTableRow } from "./dataTableRow/DataTableRow";
18
+ import { DataTableFilters } from "./dataTableFilters/DataTableFilters";
19
+ import { useDataTableState } from "./useDataTableState";
20
+ import { DataTableFilterState, DataTableSortState, flattenColumns, getActionsWidth } from "./dataTable.utils";
21
+ export type {
22
+ DataTableDateRangeFilterValue, DataTableFilterState, DataTableSortDirection,
23
+ DataTableSortState, FilterValue,
24
+ } from "./dataTable.utils";
25
+ export type DataTableProps<T> = {
26
+ id: string;
27
+ name?: string;
28
+ className?: string;
29
+ children?: JSX.Element;
30
+ element?: JSX.Element;
31
+ data: T[];
32
+ emptyMessage?: string;
33
+ actionsColumnWidth?: number;
34
+ actions?: dataTableActionsInterface<T>[];
35
+ footerAction?: (props: {
36
+ close: () => void;
37
+ selectedRows: T[];
38
+ }) => JSX.Element;
39
+ columns: dataTableColumnsInterface<T>[];
40
+ loading?: boolean;
41
+ onRowClick?: (row: any) => void;
42
+ onColumnClick?: (column: any) => void;
43
+ onLoadMore?: () => void;
44
+ hasMore?: boolean; // pour savoir s'il reste des données à charger
45
+ onSelectionChange?: (selectedRows: T[]) => void;
46
+ autoFocusFirstEditable?: boolean;
47
+ hideToggleAll?: boolean;
48
+ limitSelect?: number;
49
+ getRowId?: (row: T) => string | number;
50
+ onRowStyle?: (row: T) => React.CSSProperties | undefined;
51
+ onRowClassName?: (row: T) => string | undefined;
52
+ rowStyle?: (row: T) => React.CSSProperties | undefined;
53
+ rowClassName?: (row: T) => string | undefined;
54
+ mobileCardView?: boolean;
55
+ onFiltersChange?: (next: DataTableFilterState) => void;
56
+ onSortChange?: (next: DataTableSortState | null) => void;
57
+ onEditColumnByOverlay?: (row: T) => void;
58
+ };
59
+ export function DataTable<T>(props: DataTableProps<T>) {
60
+ const {
61
+ id,
62
+ name,
63
+ className,
64
+ children,
65
+ element,
66
+ data: sourceData = [],
67
+ actionsColumnWidth: explicitActionsWidth,
68
+ actions = [],
69
+ footerAction,
70
+ columns: groupedColumns = [],
71
+ loading,
72
+ onRowClick,
73
+ onColumnClick,
74
+ onLoadMore,
75
+ emptyMessage,
76
+ hasMore,
77
+ onSelectionChange,
78
+ autoFocusFirstEditable = false,
79
+ hideToggleAll = false,
80
+ limitSelect = undefined,
81
+ getRowId,
82
+ onRowStyle,
83
+ onRowClassName,
84
+ rowStyle,
85
+ rowClassName,
86
+ mobileCardView = false,
87
+ onFiltersChange,
88
+ onSortChange,
89
+ onEditColumnByOverlay,
90
+ } = props;
91
+ const tableState = useDataTableState(id, groupedColumns, sourceData, onFiltersChange, onSortChange);
92
+ const { flatColumns: columns, sortedData: data } = tableState;
93
+ const actionsColumnWidth = getActionsWidth(actions, explicitActionsWidth);
94
+ const filterRowRef = useRef<HTMLTableRowElement>(null);
95
+ const groupRowRef = useRef<HTMLTableRowElement>(null);
96
+ const [filterHeight, setFilterHeight] = useState(0);
97
+ const [groupHeight, setGroupHeight] = useState(0);
98
+ const hasGroups = groupedColumns.some((column) => column.children?.length);
99
+ const stickyOffsets = useMemo(() => {
100
+ const offsets = new Map<string, number>();
101
+ let left = onSelectionChange ? 40 : 0;
102
+ columns.forEach((column) => {
103
+ if (!column.sticky) return;
104
+ offsets.set(column.id, left);
105
+ const width = column.width ?? column.minWidth ?? column.maxWidth ?? 100;
106
+ left += typeof width === "number" ? width : Number.parseFloat(width);
107
+ });
108
+ return offsets;
109
+ }, [columns, onSelectionChange]);
110
+
111
+ useLayoutEffect(() => {
112
+ const measure = () => {
113
+ setFilterHeight(filterRowRef.current?.getBoundingClientRect().height ?? 0);
114
+ setGroupHeight(groupRowRef.current?.getBoundingClientRect().height ?? 0);
115
+ };
116
+ measure();
117
+ if (typeof ResizeObserver === "undefined") {
118
+ window.addEventListener("resize", measure);
119
+ return () => window.removeEventListener("resize", measure);
120
+ }
121
+ const observer = new ResizeObserver(measure);
122
+ if (filterRowRef.current) observer.observe(filterRowRef.current);
123
+ if (groupRowRef.current) observer.observe(groupRowRef.current);
124
+ return () => observer.disconnect();
125
+ }, [tableState.visibleFilters.size, hasGroups, groupedColumns]);
126
+
127
+ const isMobile = useIsMobile();
128
+ const showMobileCardView = mobileCardView && isMobile;
129
+ const loaderRef = useRef<HTMLDivElement | null>(null);
130
+ const footerActionRef = useRef<HTMLDivElement | null>(null);
131
+ const hasAutoFocusedRef = useRef(false);
132
+ const [isFooterActionVisible, setIsFooterActionVisible] = useState(false);
133
+ const [footerActionOffset, setFooterActionOffset] = useState(0);
134
+ const [selectedRowIds, setSelectedRowIds] = useState<Set<string | number>>(
135
+ new Set(),
136
+ );
137
+
138
+ const focusEditableElement = useCallback((element: HTMLElement | null) => {
139
+ if (!element) {
140
+ return;
141
+ }
142
+
143
+ element.scrollIntoView({ block: "nearest", inline: "nearest" });
144
+
145
+ requestAnimationFrame(() => {
146
+ element.focus();
147
+
148
+ if (
149
+ element instanceof HTMLInputElement ||
150
+ element instanceof HTMLTextAreaElement
151
+ ) {
152
+ element.select();
153
+ }
154
+ });
155
+ }, []);
156
+
157
+ const isInteractiveClick = useCallback((target: EventTarget | null) => {
158
+ const element =
159
+ target instanceof Element
160
+ ? target
161
+ : target instanceof Node
162
+ ? target.parentElement
163
+ : null;
164
+
165
+ if (!element) {
166
+ return false;
167
+ }
168
+
169
+ return Boolean(
170
+ element.closest(
171
+ "input,button,a,select,textarea,label,option,[role='button'],[role='checkbox'],[role='option'],[contenteditable='true'],[data-stop-row-click='true']",
172
+ ),
173
+ );
174
+ }, []);
175
+
176
+ const toggleSelectAll = useCallback(() => {
177
+ if (selectedRowIds.size === data.length) {
178
+ setSelectedRowIds(new Set());
179
+ onSelectionChange?.([]);
180
+ setIsFooterActionVisible(false);
181
+ } else {
182
+ let allIds = data.map((d) =>
183
+ getRowId ? getRowId(d) : ((d as any).id ?? d),
184
+ );
185
+
186
+ if (limitSelect && allIds.length > limitSelect) {
187
+ allIds = allIds.slice(0, limitSelect);
188
+ }
189
+
190
+ const newSelection = new Set(allIds);
191
+ setSelectedRowIds(newSelection);
192
+ setIsFooterActionVisible(true);
193
+ onSelectionChange?.(
194
+ data.filter((d) =>
195
+ newSelection.has(getRowId ? getRowId(d) : ((d as any).id ?? d)),
196
+ ),
197
+ );
198
+ }
199
+ }, [data, onSelectionChange, limitSelect, getRowId, selectedRowIds.size]);
200
+
201
+ const focusNextColumn = useCallback(
202
+ (columnIndex: number, rowIndex: number) => {
203
+ if (typeof document === "undefined") {
204
+ return;
205
+ }
206
+
207
+ const table =
208
+ document.getElementById(`${id}-mobile`) ??
209
+ document.getElementById(id);
210
+ const eligibleColumnIndexes = columns.reduce<number[]>(
211
+ (indexes, column, idx) => {
212
+ if (
213
+ column.editable &&
214
+ column.enterToNextColumn &&
215
+ column.type !== "select" &&
216
+ column.type !== "image"
217
+ ) {
218
+ indexes.push(idx);
219
+ }
220
+
221
+ return indexes;
222
+ },
223
+ [],
224
+ );
225
+
226
+ if (!table || eligibleColumnIndexes.length === 0) {
227
+ return;
228
+ }
229
+
230
+ const currentEligibleIndex = eligibleColumnIndexes.indexOf(columnIndex);
231
+
232
+ if (currentEligibleIndex === -1) {
233
+ return;
234
+ }
235
+
236
+ const editableCells = data.flatMap((_, currentRowIndex) =>
237
+ eligibleColumnIndexes.map((currentColumnIndex) => ({
238
+ rowIndex: currentRowIndex,
239
+ columnIndex: currentColumnIndex,
240
+ })),
241
+ );
242
+
243
+ const currentCellIndex = editableCells.findIndex(
244
+ (cell) =>
245
+ cell.rowIndex === rowIndex && cell.columnIndex === columnIndex,
246
+ );
247
+
248
+ if (currentCellIndex === -1) {
249
+ return;
250
+ }
251
+
252
+ const focusableSelector =
253
+ 'input:not([disabled]), textarea:not([disabled]), [contenteditable="true"]';
254
+ let nextInput: HTMLElement | null = null;
255
+
256
+ for (let offset = 1; offset <= editableCells.length; offset += 1) {
257
+ const nextCell =
258
+ editableCells[(currentCellIndex + offset) % editableCells.length];
259
+ const cell = table.querySelector(
260
+ `tbody td[data-row-index="${nextCell.rowIndex}"][data-column-index="${nextCell.columnIndex}"]`,
261
+ );
262
+
263
+ if (!(cell instanceof HTMLElement)) {
264
+ continue;
265
+ }
266
+
267
+ const focusableElement =
268
+ cell.querySelector<HTMLElement>(focusableSelector);
269
+
270
+ if (focusableElement) {
271
+ nextInput = focusableElement;
272
+ break;
273
+ }
274
+ }
275
+
276
+ if (!nextInput) {
277
+ return;
278
+ }
279
+
280
+ focusEditableElement(nextInput);
281
+ },
282
+ [columns, data, focusEditableElement, id],
283
+ );
284
+
285
+ const focusNextRow = useCallback(
286
+ (rowIndex: number) => {
287
+ if (typeof document === "undefined") {
288
+ return;
289
+ }
290
+
291
+ const table =
292
+ document.getElementById(`${id}-mobile`) ?? document.getElementById(id);
293
+ if (!table) {
294
+ return;
295
+ }
296
+
297
+ const eligibleColumnIndexes = columns.reduce<number[]>(
298
+ (indexes, column, idx) => {
299
+ if (
300
+ column.editable &&
301
+ column.type !== "select" &&
302
+ column.type !== "image"
303
+ ) {
304
+ indexes.push(idx);
305
+ }
306
+
307
+ return indexes;
308
+ },
309
+ [],
310
+ );
311
+
312
+ if (eligibleColumnIndexes.length === 0) {
313
+ return;
314
+ }
315
+
316
+ const firstEligibleColumnIndex = eligibleColumnIndexes[0];
317
+ const focusableSelector =
318
+ 'input:not([disabled]), textarea:not([disabled]), [contenteditable="true"]';
319
+ let nextInput: HTMLElement | null = null;
320
+
321
+ for (let offset = 1; offset <= data.length; offset += 1) {
322
+ const nextRowIndex = (rowIndex + offset) % data.length;
323
+ const cell = table.querySelector(
324
+ `[data-row-index="${nextRowIndex}"][data-column-index="${firstEligibleColumnIndex}"]`,
325
+ );
326
+
327
+ if (!(cell instanceof HTMLElement)) {
328
+ continue;
329
+ }
330
+
331
+ const focusableElement =
332
+ cell.querySelector<HTMLElement>(focusableSelector);
333
+
334
+ if (focusableElement) {
335
+ nextInput = focusableElement;
336
+ break;
337
+ }
338
+ }
339
+
340
+ if (!nextInput) {
341
+ return;
342
+ }
343
+
344
+ focusEditableElement(nextInput);
345
+ },
346
+ [columns, data.length, focusEditableElement, id],
347
+ );
348
+
349
+ const handleClickRow = useCallback(
350
+ (e: React.MouseEvent, row: T) => {
351
+ e.stopPropagation();
352
+
353
+ if (isInteractiveClick(e.target)) {
354
+ return;
355
+ }
356
+
357
+ const selectedText =
358
+ typeof window !== "undefined"
359
+ ? window.getSelection?.()?.toString().trim()
360
+ : "";
361
+ if (selectedText) {
362
+ return;
363
+ }
364
+
365
+ if (onRowClick) {
366
+ onRowClick?.(row);
367
+ }
368
+ },
369
+ [isInteractiveClick, onRowClick],
370
+ );
371
+ const toggleRow = useCallback(
372
+ (e: React.ChangeEvent<HTMLInputElement>, row: T) => {
373
+ e.stopPropagation();
374
+ const rowId = getRowId ? getRowId(row) : ((row as any).id ?? row);
375
+ const newSelection = new Set(selectedRowIds);
376
+
377
+ if (newSelection.has(rowId)) {
378
+ newSelection.delete(rowId);
379
+ } else {
380
+ if (limitSelect && newSelection.size >= limitSelect) {
381
+ // 🚫 limite atteinte
382
+ return;
383
+ }
384
+ newSelection.add(rowId);
385
+ }
386
+
387
+ setSelectedRowIds(newSelection);
388
+ setIsFooterActionVisible(newSelection.size > 0);
389
+ const selectedItems = data.filter((d) =>
390
+ newSelection.has(getRowId ? getRowId(d) : ((d as any).id ?? d)),
391
+ );
392
+ onSelectionChange?.(selectedItems);
393
+ },
394
+ [data, getRowId, limitSelect, onSelectionChange, selectedRowIds],
395
+ );
396
+ const showFooterAction = Boolean(footerAction && isFooterActionVisible);
397
+ const closeFooterAction = useCallback(() => {
398
+ setIsFooterActionVisible(false);
399
+ }, []);
400
+ const selectedRows = data.filter((d) =>
401
+ selectedRowIds.has(getRowId ? getRowId(d) : ((d as any).id ?? d)),
402
+ );
403
+ useEffect(() => {
404
+ if (loading || !autoFocusFirstEditable || data.length === 0) {
405
+ hasAutoFocusedRef.current = false;
406
+ return;
407
+ }
408
+
409
+ if (hasAutoFocusedRef.current) {
410
+ return;
411
+ }
412
+
413
+ const firstEditableColumnIndex = columns.findIndex(
414
+ (column) =>
415
+ column.editable &&
416
+ column.enterToNextColumn &&
417
+ column.type !== "select" &&
418
+ column.type !== "image",
419
+ );
420
+
421
+ if (firstEditableColumnIndex === -1) {
422
+ return;
423
+ }
424
+
425
+ const table =
426
+ document.getElementById(`${id}-mobile`) ?? document.getElementById(id);
427
+ const firstCell = table?.querySelector(
428
+ `[data-row-index="0"][data-column-index="${firstEditableColumnIndex}"]`,
429
+ );
430
+
431
+ if (!(firstCell instanceof HTMLElement)) {
432
+ return;
433
+ }
434
+
435
+ const firstInput = firstCell.querySelector<HTMLElement>(
436
+ 'input:not([disabled]), textarea:not([disabled]), [contenteditable="true"]',
437
+ );
438
+
439
+ if (!firstInput) {
440
+ return;
441
+ }
442
+
443
+ hasAutoFocusedRef.current = true;
444
+ focusEditableElement(firstInput);
445
+ }, [
446
+ autoFocusFirstEditable,
447
+ columns,
448
+ data.length,
449
+ focusEditableElement,
450
+ id,
451
+ loading,
452
+ ]);
453
+ useEffect(() => {
454
+ if (!showFooterAction) {
455
+ setFooterActionOffset(0);
456
+ return;
457
+ }
458
+
459
+ const footerActionElement = footerActionRef.current;
460
+
461
+ if (!footerActionElement) {
462
+ return;
463
+ }
464
+
465
+ const updateFooterActionOffset = () => {
466
+ const { height } = footerActionElement.getBoundingClientRect();
467
+ setFooterActionOffset(Math.ceil(height) + 16);
468
+ };
469
+
470
+ updateFooterActionOffset();
471
+
472
+ if (typeof ResizeObserver === "undefined") {
473
+ return;
474
+ }
475
+
476
+ const resizeObserver = new ResizeObserver(() => {
477
+ updateFooterActionOffset();
478
+ });
479
+
480
+ resizeObserver.observe(footerActionElement);
481
+
482
+ return () => {
483
+ resizeObserver.disconnect();
484
+ };
485
+ }, [showFooterAction]);
486
+ useEffect(() => {
487
+ if (!onLoadMore || !hasMore) return;
488
+
489
+ const observer = new IntersectionObserver(
490
+ (entries) => {
491
+ if (entries[0].isIntersecting) {
492
+ onLoadMore();
493
+ }
494
+ },
495
+ {
496
+ root: null,
497
+ rootMargin: "0px",
498
+ threshold: 1.0,
499
+ },
500
+ );
501
+
502
+ if (loaderRef.current) {
503
+ observer.observe(loaderRef.current);
504
+ }
505
+
506
+ return () => {
507
+ if (loaderRef.current) {
508
+ observer.unobserve(loaderRef.current);
509
+ }
510
+ };
511
+ }, [onLoadMore, hasMore]);
512
+ return (
513
+ <div
514
+ className={`data-table-container ${showFooterAction ? "data-table-container--with-footer-action" : ""}`}
515
+ style={
516
+ showFooterAction
517
+ ? { paddingBottom: `${footerActionOffset}px` }
518
+ : undefined
519
+ }
520
+ >
521
+ {name && <h2 className="mb-2">{name}</h2>}
522
+ {element}
523
+ <DataTableFilters currentFilters={tableState.filters} setCurrentFilters={tableState.setFilters} />
524
+ {showMobileCardView && columns.some((column) => column.filterable || column.sortable) && (
525
+ <div className="data-table-mobile-controls">
526
+ {columns.filter((column) => column.filterable || column.sortable).map((column, idx) => (
527
+ <div key={column.id}>
528
+ <table><thead><tr><DataTableColumn column={column} idx={idx}
529
+ isFilterVisible={tableState.visibleFilters.has(column.name ?? column.id)}
530
+ onToggleFilter={tableState.toggleFilter} onToggleSort={tableState.toggleSort}
531
+ sortDirection={tableState.sort?.key === (column.name ?? column.id) ? tableState.sort.direction : null}
532
+ /></tr></thead></table>
533
+ {tableState.renderFilter(column)}
534
+ </div>
535
+ ))}
536
+ </div>
537
+ )}
538
+ {showMobileCardView && (
539
+ <div
540
+ id={`${id}-mobile`}
541
+ className={`data-table-mobile-cards ${className ? className : ""}`}
542
+ >
543
+ {loading && <Loader display={loading} />}
544
+ {!loading &&
545
+ data.map((row, rowIndex) => {
546
+ const customStyle = (onRowStyle ?? rowStyle)?.(row);
547
+ const customClass =
548
+ (onRowClassName ?? rowClassName)?.(row) ?? "";
549
+ const rowId = getRowId
550
+ ? getRowId(row)
551
+ : ((row as any).id ?? row);
552
+
553
+ return (
554
+ <Card
555
+ key={rowId as React.Key}
556
+ item={{ title: "" }}
557
+ className={`data-table-mobile-card ${customClass}`.trim()}
558
+ handleClick={onRowClick ? () => onRowClick(row) : undefined}
559
+ >
560
+ <div
561
+ className="data-table-mobile-card__content"
562
+ style={customStyle}
563
+ data-row-index={rowIndex}
564
+ onMouseDown={(event) => {
565
+ if (isInteractiveClick(event.target)) {
566
+ event.stopPropagation();
567
+ }
568
+ }}
569
+ >
570
+ {onSelectionChange && (
571
+ <div className="data-table-mobile-card__field data-table-mobile-card__selection">
572
+ <span className="data-table-mobile-card__label">
573
+ Sélection
574
+ </span>
575
+ <Input
576
+ id={`select-card-${rowIndex}`}
577
+ name={`select-card-${rowIndex}`}
578
+ type={TypeInputEnum.CHECKBOX}
579
+ disabled={
580
+ limitSelect !== undefined &&
581
+ selectedRowIds.size >= limitSelect &&
582
+ !selectedRowIds.has(rowId)
583
+ }
584
+ checked={selectedRowIds.has(rowId)}
585
+ onChange={(event) => toggleRow(event, row)}
586
+ />
587
+ </div>
588
+ )}
589
+ {columns.map((column, columnIndex) => (
590
+ <div
591
+ className="data-table-mobile-card__field"
592
+ key={column.id}
593
+ >
594
+ <span className="data-table-mobile-card__label">
595
+ {column.label}
596
+ </span>
597
+ <DataTableRow
598
+ container="card"
599
+ column={column}
600
+ idx={columnIndex}
601
+ row={row}
602
+ rowIndex={rowIndex}
603
+ onEnterNextColumn={focusNextColumn}
604
+ onEditColumnByOverlay={onEditColumnByOverlay}
605
+ />
606
+ </div>
607
+ ))}
608
+ {actions.length > 0 && (
609
+ <div className="data-table-mobile-card__actions card-actions">
610
+ {actions.map((action, actionIndex) => (
611
+ <DataTableAction
612
+ key={action.id}
613
+ action={action}
614
+ row={row}
615
+ rowIndex={rowIndex}
616
+ index={actionIndex}
617
+ onFocusNextRow={focusNextRow}
618
+ />
619
+ ))}
620
+ </div>
621
+ )}
622
+ </div>
623
+ </Card>
624
+ );
625
+ })}
626
+ {!loading && data.length === 0 && (
627
+ <div className="data-table-mobile-cards__empty">
628
+ {emptyMessage ?? "Aucune donnée"}
629
+ </div>
630
+ )}
631
+ </div>
632
+ )}
633
+ <table
634
+ className={`data-table ${className ? className : ""}`}
635
+ id={id}
636
+ style={showMobileCardView ? { display: "none" } : undefined}
637
+ >
638
+ <colgroup>
639
+ {onSelectionChange && <col style={{ width: 40 }} />}
640
+ {columns.map((column) => <col key={column.id} style={{ width: column.width, minWidth: column.minWidth, maxWidth: column.maxWidth }} />)}
641
+ {actions.length > 0 && <col style={{ width: actionsColumnWidth }} />}
642
+ </colgroup>
643
+ <thead>
644
+ {tableState.visibleFilters.size > 0 && <tr ref={filterRowRef}>
645
+ {onSelectionChange && <th />}
646
+ {columns.map((column) => <th className="filter-row-cell" key={column.id}
647
+ style={{ position: "sticky", top: 0, zIndex: 14, background: "#f9fbfc", minWidth: column.minWidth,
648
+ ...(column.sticky ? { left: stickyOffsets.get(column.id) } : {}) }}>
649
+ {tableState.renderFilter(column)}
650
+ </th>)}
651
+ {actions.length > 0 && <th />}
652
+ </tr>}
653
+ {hasGroups && <tr ref={groupRowRef} className="data-table-group-row">
654
+ {onSelectionChange && <th />}
655
+ {groupedColumns.map((column) => <th key={column.id} colSpan={flattenColumns([column]).length}
656
+ style={{ top: filterHeight }}>{column.children?.length ? column.label : ""}</th>)}
657
+ {actions.length > 0 && <th />}
658
+ </tr>}
659
+ <tr>
660
+ {onSelectionChange && (
661
+ <th
662
+ className={`select-cell ${hideToggleAll ? "hidden" : ""}`}
663
+ style={{ width: "40px", textAlign: "center", top: filterHeight + groupHeight,
664
+ ...(stickyOffsets.size > 0 ? { left: 0, zIndex: 12 } : {}) }}
665
+ >
666
+ <Input
667
+ id={`select-th-${id}`}
668
+ name={`select-th-${id}`}
669
+ type={TypeInputEnum.CHECKBOX}
670
+ checked={
671
+ data.length > 0 && selectedRowIds.size === data.length
672
+ }
673
+ onChange={toggleSelectAll}
674
+ />
675
+ </th>
676
+ )}
677
+ {columns.map((column, idx) => (
678
+ <DataTableColumn
679
+ key={idx}
680
+ column={column}
681
+ idx={idx}
682
+ onColumnClick={onColumnClick}
683
+ stickyLeft={stickyOffsets.get(column.id)}
684
+ stickyTop={filterHeight + groupHeight}
685
+ isFilterVisible={tableState.visibleFilters.has(column.name ?? column.id)}
686
+ onToggleFilter={tableState.toggleFilter}
687
+ onToggleSort={tableState.toggleSort}
688
+ sortDirection={tableState.sort?.key === (column.name ?? column.id) ? tableState.sort.direction : null}
689
+ />
690
+ ))}
691
+ {actions.length > 0 && (
692
+ <th
693
+ style={{
694
+ width: `${actionsColumnWidth}px`,
695
+ minWidth: `${actionsColumnWidth}px`,
696
+ }}
697
+ align="right"
698
+ key={"actions"}
699
+ ></th>
700
+ )}
701
+ </tr>
702
+ </thead>
703
+ <tbody>
704
+ {loading ? (
705
+ <tr>
706
+ <td
707
+ align="center"
708
+ colSpan={columns.length + (actions.length > 0 ? 1 : 0) + (onSelectionChange ? 1 : 0)}
709
+ >
710
+ <div className="mt-3">
711
+ <Loader display={loading} />
712
+ </div>
713
+ </td>
714
+ </tr>
715
+ ) : (
716
+ <></>
717
+ )}
718
+ {!loading &&
719
+ data.map((row, rowIndex) => {
720
+ const customStyle = (onRowStyle ?? rowStyle)?.(row);
721
+ const customClass = (onRowClassName ?? rowClassName)?.(row) ?? "";
722
+
723
+ return (
724
+ <tr
725
+ key={rowIndex}
726
+ className={`${rowIndex % 2 === 0 ? "even" : "odd"} ${customClass}`.trim()}
727
+ style={customStyle}
728
+ data-row-index={rowIndex}
729
+ onClick={(e) => handleClickRow?.(e, row)}
730
+ >
731
+ {onSelectionChange && (
732
+ <td
733
+ className="select-cell"
734
+ style={{ textAlign: "center" }}
735
+ onClick={(e) => {
736
+ e.stopPropagation();
737
+ setIsFooterActionVisible(true);
738
+ }}
739
+ >
740
+ <Input
741
+ id={`select-row-${rowIndex}`}
742
+ name={`select-row-${rowIndex}`}
743
+ type={TypeInputEnum.CHECKBOX}
744
+ disabled={
745
+ limitSelect !== undefined &&
746
+ selectedRowIds.size >= limitSelect &&
747
+ !selectedRowIds.has(
748
+ getRowId ? getRowId(row) : ((row as any).id ?? row),
749
+ )
750
+ }
751
+ checked={selectedRowIds.has(
752
+ getRowId ? getRowId(row) : ((row as any).id ?? row),
753
+ )}
754
+ onChange={(e) => toggleRow(e, row)}
755
+ />
756
+ </td>
757
+ )}
758
+ {columns.map((column, idx) => (
759
+ <DataTableRow
760
+ column={column}
761
+ idx={idx}
762
+ row={row}
763
+ rowIndex={rowIndex}
764
+ stickyLeft={stickyOffsets.get(column.id)}
765
+ onEditColumnByOverlay={onEditColumnByOverlay}
766
+ key={idx}
767
+ onEnterNextColumn={focusNextColumn}
768
+ />
769
+ ))}
770
+ {actions.length > 0 && (
771
+ <td
772
+ style={{
773
+ width: `${actionsColumnWidth}px`,
774
+ minWidth: `${actionsColumnWidth}px`,
775
+ }}
776
+ align="right"
777
+ onClick={(e) => e.stopPropagation()}
778
+ >
779
+ <div className="d-flex align-items-center justify-content-end">
780
+ {actions.map((action, index) => (
781
+ <DataTableAction
782
+ key={index}
783
+ action={action}
784
+ row={row}
785
+ rowIndex={rowIndex}
786
+ index={index}
787
+ onFocusNextRow={focusNextRow}
788
+ />
789
+ ))}
790
+ </div>
791
+ </td>
792
+ )}
793
+ </tr>
794
+ );
795
+ })}
796
+ {!loading && data.length === 0 && (
797
+ <DataTableLoading
798
+ columns={columns}
799
+ actions={actions}
800
+ emptyMessage={emptyMessage}
801
+ hasSelection={Boolean(onSelectionChange)}
802
+ />
803
+ )}
804
+ </tbody>
805
+ </table>
806
+
807
+ {showFooterAction &&
808
+ typeof document !== "undefined" &&
809
+ createPortal(
810
+ <div
811
+ ref={footerActionRef}
812
+ className="data-table-footer-action"
813
+ data-footer-action="true"
814
+ >
815
+ <div className="data-table-footer-action__content">
816
+ <div
817
+ className="data-table-footer-action__close"
818
+ aria-label="Fermer les actions"
819
+ onClick={closeFooterAction}
820
+ >
821
+ <Icon classIcon={"bi-x"} />
822
+ </div>
823
+ {footerAction?.({
824
+ close: closeFooterAction,
825
+ selectedRows,
826
+ })}
827
+ </div>
828
+ </div>,
829
+ document.body,
830
+ )}
831
+
832
+ {children}
833
+ {hasMore && (
834
+ <div ref={loaderRef} className="infinite-scroll-loader">
835
+ <Loader display={true} />
836
+ </div>
837
+ )}
838
+ </div>
839
+ );
840
+ }