@addev-be/ui 0.1.18

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 (98) hide show
  1. package/assets/fonts/montserrat-200.woff2 +0 -0
  2. package/assets/fonts/montserrat-400.woff2 +0 -0
  3. package/assets/fonts/montserrat-500.woff2 +0 -0
  4. package/assets/fonts/montserrat-700.woff2 +0 -0
  5. package/assets/icons/arrow-down-a-z.svg +1 -0
  6. package/assets/icons/arrow-up-z-a.svg +1 -0
  7. package/assets/icons/arrows-rotate.svg +1 -0
  8. package/assets/icons/arrows-up-down.svg +1 -0
  9. package/assets/icons/check.svg +1 -0
  10. package/assets/icons/copy.svg +1 -0
  11. package/assets/icons/down.svg +1 -0
  12. package/assets/icons/filter-full.svg +1 -0
  13. package/assets/icons/filter-slash.svg +1 -0
  14. package/assets/icons/filter.svg +1 -0
  15. package/assets/icons/hashtag.svg +1 -0
  16. package/assets/icons/image-slash.svg +1 -0
  17. package/assets/icons/left.svg +1 -0
  18. package/assets/icons/magnifier.svg +1 -0
  19. package/assets/icons/phone.svg +1 -0
  20. package/assets/icons/plus.svg +1 -0
  21. package/assets/icons/right.svg +1 -0
  22. package/assets/icons/spinner-third.svg +1 -0
  23. package/assets/icons/table-columns.svg +1 -0
  24. package/assets/icons/up.svg +1 -0
  25. package/assets/icons/user-tie.svg +1 -0
  26. package/eslint.config.js +28 -0
  27. package/package.json +49 -0
  28. package/src/Icons.tsx +80 -0
  29. package/src/components/data/DataGrid/AdvancedRequestDataGrid.tsx +236 -0
  30. package/src/components/data/DataGrid/DataGridCell.tsx +78 -0
  31. package/src/components/data/DataGrid/DataGridColumnsModal/helpers.ts +14 -0
  32. package/src/components/data/DataGrid/DataGridColumnsModal/hooks.tsx +58 -0
  33. package/src/components/data/DataGrid/DataGridColumnsModal/index.tsx +181 -0
  34. package/src/components/data/DataGrid/DataGridColumnsModal/styles.ts +104 -0
  35. package/src/components/data/DataGrid/DataGridEditableCell.tsx +54 -0
  36. package/src/components/data/DataGrid/DataGridFilterMenu/hooks.tsx +75 -0
  37. package/src/components/data/DataGrid/DataGridFilterMenu/index.tsx +190 -0
  38. package/src/components/data/DataGrid/DataGridFilterMenu/styles.ts +100 -0
  39. package/src/components/data/DataGrid/DataGridFooter.tsx +64 -0
  40. package/src/components/data/DataGrid/DataGridHeader.tsx +129 -0
  41. package/src/components/data/DataGrid/DataGridHeaderCell.tsx +166 -0
  42. package/src/components/data/DataGrid/FilterModalContent/index.tsx +125 -0
  43. package/src/components/data/DataGrid/FilterModalContent/styles.ts +22 -0
  44. package/src/components/data/DataGrid/FilterValuesScroller.tsx +131 -0
  45. package/src/components/data/DataGrid/VirtualScroller.tsx +51 -0
  46. package/src/components/data/DataGrid/helpers/advancedRequests.tsx +61 -0
  47. package/src/components/data/DataGrid/helpers/columns.tsx +259 -0
  48. package/src/components/data/DataGrid/helpers/filters.ts +219 -0
  49. package/src/components/data/DataGrid/helpers/index.ts +3 -0
  50. package/src/components/data/DataGrid/hooks/index.ts +30 -0
  51. package/src/components/data/DataGrid/hooks/useDataGrid.tsx +225 -0
  52. package/src/components/data/DataGrid/hooks/useDataGridCopy.ts +166 -0
  53. package/src/components/data/DataGrid/hooks/useDataGridSettings.ts +49 -0
  54. package/src/components/data/DataGrid/index.tsx +145 -0
  55. package/src/components/data/DataGrid/styles.ts +284 -0
  56. package/src/components/data/DataGrid/types.ts +232 -0
  57. package/src/components/data/index.ts +3 -0
  58. package/src/components/forms/Button.tsx +99 -0
  59. package/src/components/forms/IconButton.tsx +57 -0
  60. package/src/components/forms/IndeterminateCheckbox.tsx +46 -0
  61. package/src/components/forms/Select.tsx +40 -0
  62. package/src/components/forms/index.ts +5 -0
  63. package/src/components/forms/styles.ts +20 -0
  64. package/src/components/index.ts +3 -0
  65. package/src/components/layout/Dropdown/index.tsx +79 -0
  66. package/src/components/layout/Dropdown/styles.ts +44 -0
  67. package/src/components/layout/Loading/index.tsx +28 -0
  68. package/src/components/layout/Loading/styles.ts +29 -0
  69. package/src/components/layout/Modal/index.tsx +51 -0
  70. package/src/components/layout/Modal/styles.ts +110 -0
  71. package/src/components/layout/index.ts +3 -0
  72. package/src/config/index.ts +14 -0
  73. package/src/helpers/getScrollbarSize.ts +14 -0
  74. package/src/helpers/numbers.ts +20 -0
  75. package/src/hooks/index.ts +2 -0
  76. package/src/hooks/useElementSize.ts +24 -0
  77. package/src/hooks/useWindowSize.ts +20 -0
  78. package/src/index.ts +7 -0
  79. package/src/providers/PortalsProvider/index.tsx +54 -0
  80. package/src/providers/PortalsProvider/styles.ts +27 -0
  81. package/src/providers/SettingsProvider/index.tsx +70 -0
  82. package/src/providers/ThemeProvider/ThemeProvider.ts +55 -0
  83. package/src/providers/ThemeProvider/defaultTheme.ts +444 -0
  84. package/src/providers/ThemeProvider/index.ts +3 -0
  85. package/src/providers/ThemeProvider/types.ts +123 -0
  86. package/src/providers/UiProviders/index.tsx +65 -0
  87. package/src/providers/UiProviders/styles.ts +10 -0
  88. package/src/providers/hooks.ts +8 -0
  89. package/src/providers/index.ts +5 -0
  90. package/src/services/WebSocketService.ts +147 -0
  91. package/src/services/advancedRequests.ts +100 -0
  92. package/src/services/base.ts +31 -0
  93. package/src/services/hooks.ts +13 -0
  94. package/src/services/index.ts +2 -0
  95. package/src/styles/animations.scss +30 -0
  96. package/src/styles/index.scss +42 -0
  97. package/src/typings.d.ts +6 -0
  98. package/tsconfig.json +18 -0
@@ -0,0 +1,64 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ /* eslint-disable @typescript-eslint/no-unnecessary-type-constraint */
3
+
4
+ import { DataGridContext } from './types';
5
+ import { getScrollBarSize } from '../../../helpers/getScrollbarSize';
6
+ import { join } from 'lodash';
7
+ import { useDataGridContext } from './hooks';
8
+
9
+ const SCROLL_BAR_SIZE = getScrollBarSize();
10
+
11
+ export const DataGridFooter = <R,>({
12
+ context,
13
+ }: {
14
+ context: DataGridContext<R>;
15
+ }) => {
16
+ const {
17
+ visibleColumns,
18
+ rows,
19
+ selectedRows,
20
+ sortedRows,
21
+ rowHeight = 48,
22
+ selectable,
23
+ } = useDataGridContext(context);
24
+
25
+ return (
26
+ <tfoot
27
+ // className="block border-t border-t-gray-300"
28
+ style={{
29
+ paddingRight: `${SCROLL_BAR_SIZE[0]}px`,
30
+ }}
31
+ >
32
+ <tr
33
+ // className="grid bg-gray-100"
34
+ style={{
35
+ height: `${rowHeight}px`,
36
+ }}
37
+ >
38
+ {!!selectable && (
39
+ <th
40
+ // className="inline-block w-12 select-none"
41
+ key="__select_checkbox__"
42
+ ></th>
43
+ )}
44
+ {visibleColumns.map(([key, col]) => (
45
+ <th
46
+ key={key}
47
+ className={join(
48
+ [
49
+ // 'inline-block relative group px-3 py-0 text-left text-sm hover:bg-gray-50 leading-6',
50
+ col.className,
51
+ col.footerClassName,
52
+ // '!overflow-visible',
53
+ ],
54
+ ' '
55
+ )}
56
+ style={{ width: (col.width ?? 150) + 'px' }}
57
+ >
58
+ {col.footer?.(rows, sortedRows, selectedRows)}
59
+ </th>
60
+ ))}
61
+ </tr>
62
+ </tfoot>
63
+ );
64
+ };
@@ -0,0 +1,129 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ /* eslint-disable @typescript-eslint/no-unnecessary-type-constraint */
3
+
4
+ import * as styles from './styles';
5
+
6
+ import {
7
+ ArrowsRotateIcon,
8
+ CopyIcon,
9
+ FilterSlashIcon,
10
+ TableColumnsIcon,
11
+ } from '../../../Icons';
12
+ import { useCallback, useMemo, useState } from 'react';
13
+
14
+ import { Button } from '../../forms';
15
+ import { DataGridContext } from './types';
16
+ import { DataGridHeaderCell } from './DataGridHeaderCell';
17
+ import { IndeterminateCheckbox } from '../../forms/IndeterminateCheckbox';
18
+ import { Loading } from '../../layout';
19
+ import { useDataGridColumnsModal } from './DataGridColumnsModal/hooks';
20
+ import { useDataGridContext } from './hooks';
21
+
22
+ export const DataGridHeader = <R,>({
23
+ context,
24
+ }: {
25
+ context: DataGridContext<R>;
26
+ }) => {
27
+ const {
28
+ name,
29
+ visibleColumns,
30
+ selectable,
31
+ rows,
32
+ selectedRows,
33
+ setSelectedRows,
34
+ copyTable,
35
+ setFilters,
36
+ refresh,
37
+ headerColor,
38
+ } = useDataGridContext(context);
39
+ const [visibleFilter, setVisibleFilter] = useState<string | undefined>();
40
+
41
+ const { openModal, modal } = useDataGridColumnsModal<R>(context);
42
+
43
+ const checkboxStatus =
44
+ selectedRows.length === 0
45
+ ? false
46
+ : selectedRows.length === rows.length
47
+ ? true
48
+ : undefined;
49
+ const toggleAll = useCallback(
50
+ (newStatus: boolean) => {
51
+ setSelectedRows(newStatus ? rows : []);
52
+ },
53
+ [rows, setSelectedRows]
54
+ );
55
+
56
+ const onFilterButtonClicked = useCallback((columnKey: string) => {
57
+ setVisibleFilter((prev) => (prev === columnKey ? undefined : columnKey));
58
+ }, []);
59
+
60
+ const gridTemplateColumns = useMemo(
61
+ () => visibleColumns.map(([, col]) => `${col.width ?? 150}px`).join(' '),
62
+ [visibleColumns]
63
+ );
64
+
65
+ const [isLoadingVisible, setIsLoadingVisible] = useState(false);
66
+ const runCopyTable = useCallback(() => {
67
+ setIsLoadingVisible(true);
68
+ copyTable().then(() => setIsLoadingVisible(false));
69
+ }, [copyTable]);
70
+
71
+ const toolsRow = (
72
+ <styles.DataGridToolsRow>
73
+ <Loading visible={isLoadingVisible} />
74
+ {refresh && (
75
+ <Button size="small" onClick={refresh}>
76
+ <ArrowsRotateIcon />
77
+ Rafraîchir
78
+ </Button>
79
+ )}
80
+ <Button color="emerald" size="small" onClick={runCopyTable}>
81
+ <CopyIcon />
82
+ Copier la table
83
+ </Button>
84
+ <Button size="small" color="danger" onClick={() => setFilters({})}>
85
+ <FilterSlashIcon />
86
+ Supprimer les filtres
87
+ </Button>
88
+ {name && (
89
+ <Button color="info" size="small" onClick={openModal}>
90
+ <TableColumnsIcon />
91
+ Paramètres des colonnes
92
+ </Button>
93
+ )}
94
+ </styles.DataGridToolsRow>
95
+ );
96
+
97
+ return (
98
+ <>
99
+ {modal}
100
+ {toolsRow}
101
+ <styles.DataGridHeaderRow
102
+ $gridTemplateColumns={gridTemplateColumns}
103
+ $headerColor={headerColor}
104
+ >
105
+ {!!selectable && (
106
+ <th
107
+ // className="inline-flex items-center justify-center w-12 select-none"
108
+ >
109
+ <IndeterminateCheckbox
110
+ checked={checkboxStatus}
111
+ onChange={() => toggleAll(!checkboxStatus)}
112
+ />
113
+ </th>
114
+ )}
115
+ {visibleColumns.map(([key, col], index) => (
116
+ <DataGridHeaderCell
117
+ key={key}
118
+ columnKey={key}
119
+ column={col}
120
+ context={context}
121
+ columnIndex={index}
122
+ isFilterOpen={visibleFilter === key}
123
+ onFilterButtonClicked={onFilterButtonClicked}
124
+ />
125
+ ))}
126
+ </styles.DataGridHeaderRow>
127
+ </>
128
+ );
129
+ };
@@ -0,0 +1,166 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ /* eslint-disable @typescript-eslint/no-unnecessary-type-constraint */
3
+
4
+ import * as styles from './styles';
5
+
6
+ import {
7
+ ArrowDownAZIcon,
8
+ ArrowUpZAIcon,
9
+ ArrowsUpDownIcon,
10
+ FilterFullIcon,
11
+ FilterIcon,
12
+ } from '../../../Icons';
13
+ import { MouseEvent, useCallback, useMemo, useRef, useState } from 'react';
14
+
15
+ import { DataGridFilterMenu } from './DataGridFilterMenu';
16
+ import { DataGridHeaderCellProps } from './types';
17
+ import { Dropdown } from '../../layout';
18
+ import { IconButton } from '../../forms/IconButton';
19
+ import { useDataGridContext } from './hooks';
20
+ import { useUi } from '../../../providers';
21
+
22
+ export const DataGridHeaderCell = <R,>({
23
+ columnKey,
24
+ columnIndex,
25
+ column,
26
+ context,
27
+ }: DataGridHeaderCellProps<R>) => {
28
+ const { getElementScreenRect } = useUi();
29
+ const contextValue = useDataGridContext(context);
30
+ const {
31
+ filters = {},
32
+ sorts = {},
33
+ setSorts,
34
+ setColumnWidth,
35
+ saveSettings,
36
+ headerColor,
37
+ } = contextValue;
38
+ const filterButtonRef = useRef<HTMLButtonElement | null>(null);
39
+
40
+ /** SORTING */
41
+ const SortIcon = sorts[columnKey]
42
+ ? sorts[columnKey] === 'desc'
43
+ ? ArrowUpZAIcon
44
+ : ArrowDownAZIcon
45
+ : ArrowsUpDownIcon;
46
+
47
+ const onSortButtonClicked = useCallback(
48
+ (columnKey: string) => {
49
+ const sort = sorts[columnKey];
50
+ if (!sort || sort === 'desc') {
51
+ setSorts({ [columnKey]: 'asc' });
52
+ } else {
53
+ setSorts({ [columnKey]: 'desc' });
54
+ }
55
+ },
56
+ [setSorts, sorts]
57
+ );
58
+
59
+ /** RESIZING */
60
+
61
+ const [isResizing, setIsResizing] = useState(false);
62
+ const resizingOffset = useRef(0);
63
+
64
+ const onResizeStart = useCallback(
65
+ (e: MouseEvent) => {
66
+ resizingOffset.current = e.screenX - (column.width || 150);
67
+ setIsResizing(true);
68
+ },
69
+ [column.width]
70
+ );
71
+
72
+ const onResizeMove = useCallback(
73
+ (e: MouseEvent) => {
74
+ if (isResizing) {
75
+ const newWidth = Math.max(86, e.screenX - resizingOffset.current);
76
+ setColumnWidth(columnKey, newWidth);
77
+ }
78
+ },
79
+ [columnKey, isResizing, setColumnWidth]
80
+ );
81
+
82
+ const onResizeEnd = useCallback(() => {
83
+ setIsResizing(false);
84
+ saveSettings();
85
+ }, [saveSettings]);
86
+
87
+ const [isFilterDropdownVisible, setIsFilterDropdownVisible] = useState(false);
88
+ const filterDropdown = useMemo(() => {
89
+ if (!isFilterDropdownVisible || !filterButtonRef.current || !columnKey) {
90
+ return null;
91
+ }
92
+ const filterButtonRect = getElementScreenRect(filterButtonRef.current);
93
+ filterButtonRect.x += window.scrollX;
94
+ filterButtonRect.y += window.scrollY;
95
+ return (
96
+ <Dropdown
97
+ $sourceRect={filterButtonRect}
98
+ onClose={() => setIsFilterDropdownVisible(false)}
99
+ $width={320}
100
+ $height={[200, 320]}
101
+ $autoPositionX
102
+ >
103
+ <DataGridFilterMenu
104
+ columnKey={columnKey}
105
+ columnIndex={columnIndex}
106
+ context={context}
107
+ onClose={() => setIsFilterDropdownVisible(false)}
108
+ />
109
+ </Dropdown>
110
+ );
111
+ }, [
112
+ columnIndex,
113
+ columnKey,
114
+ context,
115
+ getElementScreenRect,
116
+ isFilterDropdownVisible,
117
+ ]);
118
+
119
+ const onFilterButtonClicked = useCallback(() => {
120
+ setIsFilterDropdownVisible(true);
121
+ }, []);
122
+
123
+ return (
124
+ <styles.DataGridHeaderCellContainer
125
+ $headerColor={headerColor}
126
+ $isResizing={isResizing}
127
+ >
128
+ {filterDropdown}
129
+ <span>{column.name}</span>
130
+ {!!column.sortGetter && (
131
+ <IconButton
132
+ color={headerColor}
133
+ size="small"
134
+ icon={SortIcon}
135
+ onClick={() => onSortButtonClicked?.(columnKey)}
136
+ />
137
+ )}
138
+ {!!column.filter && (
139
+ <IconButton
140
+ size="small"
141
+ ref={filterButtonRef}
142
+ icon={
143
+ filters[columnKey]?.values.length > 0 ? FilterFullIcon : FilterIcon
144
+ }
145
+ color={filters[columnKey]?.values.length > 0 ? 'danger' : headerColor}
146
+ onClick={onFilterButtonClicked}
147
+ />
148
+ )}
149
+ {column.resizable !== false && (
150
+ <>
151
+ <styles.DataGridResizeGrip
152
+ className={isResizing ? 'active' : ''}
153
+ $headerColor={headerColor}
154
+ onMouseDown={onResizeStart}
155
+ />
156
+ {isResizing && (
157
+ <styles.ResizeBackdrop
158
+ onMouseMove={onResizeMove}
159
+ onMouseUp={onResizeEnd}
160
+ />
161
+ )}
162
+ </>
163
+ )}
164
+ </styles.DataGridHeaderCellContainer>
165
+ );
166
+ };
@@ -0,0 +1,125 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ /* eslint-disable @typescript-eslint/no-unnecessary-type-constraint */
3
+
4
+ import * as styles from './styles';
5
+
6
+ import {
7
+ DataGridFilter,
8
+ DataGridFilterOperator,
9
+ DataGridFilterType,
10
+ } from '../types';
11
+ import { Input, Select } from '../../../forms';
12
+ import { useEffect, useRef } from 'react';
13
+
14
+ // import { Select } from '../../Select';
15
+ // import clx from 'classnames';
16
+
17
+ const filterOperators: {
18
+ [K in DataGridFilterType]: Partial<{
19
+ [K2 in DataGridFilterOperator<K>]: string;
20
+ }>;
21
+ } = {
22
+ number: {
23
+ equals: 'Égal à',
24
+ notEquals: 'Différent de',
25
+ greaterThan: 'Supérieur à',
26
+ greaterThanOrEqual: 'Supérieur ou égal à',
27
+ lessThan: 'Inférieur à',
28
+ lessThanOrEqual: 'Inférieur ou égal à',
29
+ inRange: "Dans l'intervalle",
30
+ },
31
+ text: {
32
+ equals: 'Égal à',
33
+ notEquals: 'Différent de',
34
+ contains: 'Contient',
35
+ notContains: 'Ne contient pas',
36
+ startsWith: 'Commence par',
37
+ endsWith: 'Finit par',
38
+ },
39
+ };
40
+
41
+ type FilterModalContentProps<
42
+ R,
43
+ T extends DataGridFilterType = DataGridFilterType
44
+ > = {
45
+ filter: DataGridFilter<R, T>;
46
+ onFilterChanged: (filter: DataGridFilter<R, T>) => void;
47
+ };
48
+
49
+ export const FilterModalContent = <
50
+ R,
51
+ T extends DataGridFilterType = DataGridFilterType
52
+ >({
53
+ filter,
54
+ onFilterChanged,
55
+ }: FilterModalContentProps<R, T>) => {
56
+ const value1Ref = useRef<HTMLInputElement>(null);
57
+ useEffect(() => {
58
+ setTimeout(() => value1Ref.current?.focus(), 100);
59
+ }, []);
60
+
61
+ if (!filter) return null;
62
+
63
+ const { operator, type, values } = filter;
64
+ return (
65
+ <styles.FilterModalContentContainer>
66
+ <label htmlFor="filterOperator">
67
+ <span>Opérateur :</span>
68
+ <Select
69
+ name="filterOperator"
70
+ items={Object.entries(filterOperators[filter.type])}
71
+ itemKey={0}
72
+ itemLabel={1}
73
+ value={String(operator)}
74
+ className="text-sm"
75
+ onChange={(e) =>
76
+ onFilterChanged({ ...filter, operator: e.target.value as any })
77
+ }
78
+ />
79
+ </label>
80
+ <label htmlFor="filterValue1">
81
+ <span>{operator === 'inRange' ? 'Entre' : 'Valeur'} :</span>
82
+ <Input
83
+ ref={value1Ref}
84
+ name="filterValue1"
85
+ type={filter.type}
86
+ value={values[0] ?? ''}
87
+ // className={(type === 'number' ? 'w-24 ' : '') + 'text-sm'}
88
+ onChange={(e) => {
89
+ const newValues = [...values];
90
+ newValues[0] =
91
+ type === 'number'
92
+ ? Number(e.target.value)
93
+ : (String(e.target.value) as any);
94
+ onFilterChanged({
95
+ ...filter,
96
+ values: newValues,
97
+ });
98
+ }}
99
+ />
100
+ </label>
101
+ {operator === 'inRange' && (
102
+ <label htmlFor="filterValue2">
103
+ <span>et</span>
104
+ <input
105
+ name="filterValue2"
106
+ type={filter.type}
107
+ value={values[1] ?? ''}
108
+ // className="text-sm w-24"
109
+ onChange={(e) => {
110
+ const newValues = [...values];
111
+ newValues[0] =
112
+ type === 'number'
113
+ ? Number(e.target.value)
114
+ : (String(e.target.value) as any);
115
+ onFilterChanged({
116
+ ...filter,
117
+ values: newValues,
118
+ });
119
+ }}
120
+ />
121
+ </label>
122
+ )}
123
+ </styles.FilterModalContentContainer>
124
+ );
125
+ };
@@ -0,0 +1,22 @@
1
+ import styled from 'styled-components';
2
+
3
+ export const FilterModalContentContainer = styled.div.attrs({
4
+ className: 'FilterModalContentContainer',
5
+ })`
6
+ display: flex;
7
+ flex-direction: column;
8
+ font-size: var(--text-base);
9
+ gap: var(--space-1);
10
+
11
+ & > label {
12
+ display: flex;
13
+ flex-direction: row;
14
+ gap: var(--space-1);
15
+ align-items: center;
16
+
17
+ & > span {
18
+ font-weight: bold;
19
+ width: var(--space-32);
20
+ }
21
+ }
22
+ `;
@@ -0,0 +1,131 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+
3
+ import * as styles from './styles';
4
+
5
+ import {
6
+ DataGridFilterCheckbox,
7
+ DataGridFilterFormatter,
8
+ DataGridFilterGroup,
9
+ } from './types';
10
+ import { debounce, join } from 'lodash';
11
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
12
+
13
+ import { getCheckboxes } from './helpers';
14
+ import { useElementSize } from '../../../hooks';
15
+
16
+ const defaultFormatter = (value: any) => (value == null ? null : String(value));
17
+
18
+ const CheckboxTemplate = <R extends string | number | null>({
19
+ selectedValues,
20
+ value,
21
+ index,
22
+ className,
23
+ style,
24
+ onToggle,
25
+ }: {
26
+ selectedValues: R[];
27
+ value: DataGridFilterCheckbox<R>;
28
+ index: number;
29
+ className?: string;
30
+ style?: React.CSSProperties;
31
+ onToggle?: (values: R[]) => void;
32
+ }) => (
33
+ <div
34
+ key={index}
35
+ className={join(
36
+ [
37
+ // 'absolute left-0 right-0 flex flex-row cursor-pointer hover:bg-gray-50',
38
+ className,
39
+ ],
40
+ ' '
41
+ )}
42
+ style={{ ...style, paddingLeft: `${value.level}rem` }}
43
+ title={value.displayValue || '(Vides)'}
44
+ onClick={() => onToggle?.(value.values)}
45
+ >
46
+ <input
47
+ type="checkbox"
48
+ checked={selectedValues.includes(value.values[0])}
49
+ readOnly
50
+ // className="inline-block mr-2"
51
+ />
52
+ <span
53
+ // className="mr-2 truncate"
54
+ >
55
+ {value.displayValue || '(Vides)'}
56
+ </span>
57
+ </div>
58
+ );
59
+
60
+ type FilterValuesScrollerProps<R extends string | number | null> = {
61
+ values: R[];
62
+ rowHeight?: number;
63
+ formatter: DataGridFilterFormatter;
64
+ onRangeChange?: (startIndex: number, length: number) => void;
65
+ onToggle?: (values: R[]) => void;
66
+ selectedValues?: R[];
67
+ groups?: DataGridFilterGroup<R>[];
68
+ };
69
+
70
+ export const FilterValuesScroller = <R extends string | number | null>({
71
+ values,
72
+ rowHeight = styles.DEFAULT_FILTER_ROW_HEIGHT,
73
+ onRangeChange,
74
+ onToggle,
75
+ formatter = defaultFormatter,
76
+ selectedValues = [],
77
+ groups,
78
+ }: FilterValuesScrollerProps<R>) => {
79
+ const scrollableRef = useRef<HTMLDivElement | null>(null);
80
+ const { height } = useElementSize(scrollableRef.current);
81
+
82
+ const tolerance = 20;
83
+ const [scrollTop, setScrollTop] = useState(0);
84
+ const index = Math.floor(scrollTop / rowHeight);
85
+ const length = Math.ceil(height / rowHeight);
86
+
87
+ // eslint-disable-next-line react-hooks/exhaustive-deps
88
+ const onScroll = useCallback(
89
+ debounce((e: React.UIEvent<HTMLDivElement>) => {
90
+ const target = e.target as HTMLDivElement;
91
+ setScrollTop(() => target.scrollTop);
92
+ }, 50),
93
+ []
94
+ );
95
+
96
+ const checkboxes = useMemo(
97
+ () => getCheckboxes(values, formatter ?? defaultFormatter, groups),
98
+ [values, groups, formatter]
99
+ );
100
+ const visibleCheckboxes = checkboxes.slice(
101
+ Math.max(0, index - tolerance),
102
+ index + length + 1 + tolerance
103
+ );
104
+ const firstCheckboxTop = Math.max(0, index - tolerance) * rowHeight;
105
+
106
+ useEffect(() => {
107
+ onRangeChange?.(index, length);
108
+ }, [index, length, onRangeChange]);
109
+
110
+ return (
111
+ <styles.FilterValuesScrollerContainer
112
+ ref={scrollableRef}
113
+ onScroll={onScroll}
114
+ $rowHeight={rowHeight}
115
+ >
116
+ <div style={{ height: `${values.length * rowHeight}px` }}>
117
+ {visibleCheckboxes.map((value, index) => (
118
+ <CheckboxTemplate
119
+ className="checkbox"
120
+ style={{ top: firstCheckboxTop + index * rowHeight + 'px' }}
121
+ key={index}
122
+ selectedValues={selectedValues}
123
+ value={value}
124
+ index={index}
125
+ onToggle={onToggle}
126
+ />
127
+ ))}
128
+ </div>
129
+ </styles.FilterValuesScrollerContainer>
130
+ );
131
+ };
@@ -0,0 +1,51 @@
1
+ import * as styles from './styles';
2
+
3
+ import { ReactNode, useContext, useMemo } from 'react';
4
+
5
+ import { DataGridContext } from './types';
6
+
7
+ type VirtualScrollerProps<R> = {
8
+ showAllRows?: boolean;
9
+ rowTemplate: (row: R, index: number) => ReactNode;
10
+ hasFooter?: boolean;
11
+ context: DataGridContext<R>;
12
+ onRangeChange?: (startIndex: number, length: number) => void;
13
+ };
14
+
15
+ export const VirtualScroller = <R,>(props: VirtualScrollerProps<R>) => {
16
+ const {
17
+ rowHeight = styles.DEFAULT_ROW_HEIGHT,
18
+ // headerRowHeight = styles.DEFAULT_HEADER_ROW_HEIGHT,
19
+ sortedRows,
20
+ visibleColumns,
21
+ index,
22
+ visibleRows,
23
+ } = useContext(props.context);
24
+ const {
25
+ rowTemplate,
26
+ // hasFooter, onRangeChange
27
+ } = props;
28
+
29
+ const totalHeight = sortedRows.length * rowHeight;
30
+ const topPadding =
31
+ Math.max(0, index - styles.VIRTUAL_SCROLL_TOLERANCE) * rowHeight;
32
+ // const headerAndFooterHeight =
33
+ // 2 * headerRowHeight + (hasFooter ? rowHeight : 0) + 2;
34
+
35
+ const gridTemplateColumns = useMemo(
36
+ () => visibleColumns.map(([, col]) => `${col.width ?? 150}px`).join(' '),
37
+ [visibleColumns]
38
+ );
39
+
40
+ return (
41
+ <styles.VirtualScrollerContainer $height={totalHeight}>
42
+ <styles.VirtualScrollerRowsContainer
43
+ $gridTemplateColumns={gridTemplateColumns}
44
+ $topPadding={topPadding}
45
+ $rowHeight={rowHeight}
46
+ >
47
+ {visibleRows.map(rowTemplate)}
48
+ </styles.VirtualScrollerRowsContainer>
49
+ </styles.VirtualScrollerContainer>
50
+ );
51
+ };