@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,181 @@
1
+ import * as styles from './styles';
2
+
3
+ import { DataGridColumn, DataGridColumns } from '../types';
4
+ import { Dispatch, SetStateAction, useCallback } from 'react';
5
+ import { DownIcon, LeftIcon, RightIcon, UpIcon } from '../../../../Icons';
6
+
7
+ import { IconButton } from '../../../forms/IconButton';
8
+ import _ from 'lodash';
9
+ import { normalizeColumnsOrders } from './helpers';
10
+ import { useVisibleAndHiddenColumns } from '../hooks';
11
+
12
+ export const DataGridColumnItem = <R,>({
13
+ columnIndex,
14
+ columnKey,
15
+ column,
16
+ visible,
17
+ onUpButtonClicked,
18
+ onDownButtonClicked,
19
+ onVisibleButtonClicked,
20
+ }: {
21
+ columnIndex: number;
22
+ columnKey: string;
23
+ column: DataGridColumn<R>;
24
+ visible: boolean;
25
+ onUpButtonClicked?: (index: number) => void;
26
+ onDownButtonClicked?: (index: number) => void;
27
+ onVisibleButtonClicked: (key: string) => void;
28
+ }) => (
29
+ <styles.ColumnItem $visible={visible}>
30
+ {!visible && (
31
+ <styles.Buttons $visible={false}>
32
+ <IconButton
33
+ size="small"
34
+ icon={LeftIcon}
35
+ onClick={() => onVisibleButtonClicked(columnKey)}
36
+ />
37
+ </styles.Buttons>
38
+ )}
39
+ <span>{column?.name}</span>
40
+ {visible && (
41
+ <styles.Buttons $visible>
42
+ {onUpButtonClicked && (
43
+ <IconButton
44
+ size="small"
45
+ icon={UpIcon}
46
+ onClick={() => onUpButtonClicked(columnIndex)}
47
+ />
48
+ )}
49
+ {onDownButtonClicked && (
50
+ <IconButton
51
+ size="small"
52
+ icon={DownIcon}
53
+ onClick={() => onDownButtonClicked(columnIndex)}
54
+ />
55
+ )}
56
+ <IconButton
57
+ size="small"
58
+ icon={RightIcon}
59
+ onClick={() => onVisibleButtonClicked(columnKey)}
60
+ />
61
+ </styles.Buttons>
62
+ )}
63
+ </styles.ColumnItem>
64
+ );
65
+
66
+ export const DataGridColumnsEditor = <R,>({
67
+ columns,
68
+ onColumnsChanged,
69
+ }: {
70
+ columns: DataGridColumns<R>;
71
+ onColumnsChanged: Dispatch<SetStateAction<DataGridColumns<R>>>;
72
+ }) => {
73
+ const [visibleColumns, hiddenColumns] = useVisibleAndHiddenColumns(columns);
74
+ const sortedVisibleColumnsEntries = normalizeColumnsOrders(
75
+ _.sortBy(Object.entries(visibleColumns), ([, col]) => col.order ?? 0)
76
+ );
77
+
78
+ const onUpButtonClicked = useCallback(
79
+ (index: number) => {
80
+ if (index > 0) {
81
+ const newArray = normalizeColumnsOrders([
82
+ ...sortedVisibleColumnsEntries.slice(0, index - 1),
83
+ sortedVisibleColumnsEntries[index],
84
+ sortedVisibleColumnsEntries[index - 1],
85
+ ...sortedVisibleColumnsEntries.slice(index + 1),
86
+ ]);
87
+ onColumnsChanged(() => ({
88
+ ...Object.fromEntries(newArray),
89
+ ...hiddenColumns,
90
+ }));
91
+ }
92
+ },
93
+ [hiddenColumns, onColumnsChanged, sortedVisibleColumnsEntries]
94
+ );
95
+
96
+ const onDownButtonClicked = useCallback(
97
+ (index: number) => {
98
+ if (index < sortedVisibleColumnsEntries.length - 1) {
99
+ const newArray = normalizeColumnsOrders([
100
+ ...sortedVisibleColumnsEntries.slice(0, index),
101
+ sortedVisibleColumnsEntries[index + 1],
102
+ sortedVisibleColumnsEntries[index],
103
+ ...sortedVisibleColumnsEntries.slice(index + 2),
104
+ ]);
105
+ onColumnsChanged(() => ({
106
+ ...Object.fromEntries(newArray),
107
+ ...hiddenColumns,
108
+ }));
109
+ }
110
+ },
111
+ [hiddenColumns, onColumnsChanged, sortedVisibleColumnsEntries]
112
+ );
113
+
114
+ const onHideButtonClicked = useCallback(
115
+ (columnKey: string) => {
116
+ onColumnsChanged((prev) => ({
117
+ ...prev,
118
+ [columnKey]: {
119
+ ...prev[columnKey],
120
+ order: -1,
121
+ },
122
+ }));
123
+ },
124
+ [onColumnsChanged]
125
+ );
126
+
127
+ const onShowButtonClicked = useCallback(
128
+ (columnKey: string) => {
129
+ onColumnsChanged((prev) => ({
130
+ ...prev,
131
+ [columnKey]: {
132
+ ...hiddenColumns[columnKey],
133
+ order: Object.keys(prev).length,
134
+ },
135
+ }));
136
+ },
137
+ [hiddenColumns, onColumnsChanged]
138
+ );
139
+
140
+ return (
141
+ <styles.Container>
142
+ <styles.Panel>
143
+ <styles.Title>Colonnes visibles</styles.Title>
144
+ <styles.ColumnsList>
145
+ {sortedVisibleColumnsEntries.map(([key, column], index) => (
146
+ <DataGridColumnItem
147
+ key={key}
148
+ columnKey={key}
149
+ columnIndex={index}
150
+ column={column}
151
+ visible
152
+ onUpButtonClicked={index > 0 ? onUpButtonClicked : undefined}
153
+ onDownButtonClicked={
154
+ index < sortedVisibleColumnsEntries.length - 1
155
+ ? onDownButtonClicked
156
+ : undefined
157
+ }
158
+ onVisibleButtonClicked={onHideButtonClicked}
159
+ />
160
+ ))}
161
+ </styles.ColumnsList>
162
+ </styles.Panel>
163
+
164
+ <styles.Panel>
165
+ <styles.Title>Colonnes masquées</styles.Title>
166
+ <styles.ColumnsList>
167
+ {Object.entries(hiddenColumns).map(([key, column], index) => (
168
+ <DataGridColumnItem
169
+ key={key}
170
+ columnKey={key}
171
+ columnIndex={index}
172
+ column={column}
173
+ visible={false}
174
+ onVisibleButtonClicked={onShowButtonClicked}
175
+ />
176
+ ))}
177
+ </styles.ColumnsList>
178
+ </styles.Panel>
179
+ </styles.Container>
180
+ );
181
+ };
@@ -0,0 +1,104 @@
1
+ import styled, { css } from 'styled-components';
2
+
3
+ export const Container = styled.div.attrs({
4
+ className: 'DataGridColumnsModal-Container',
5
+ })`
6
+ display: flex;
7
+ flex-direction: row;
8
+ gap: var(--space-2);
9
+ overflow: auto;
10
+ font-size: var(--text-base);
11
+ height: 100%;
12
+ `;
13
+
14
+ export const Panel = styled.div.attrs({
15
+ className: 'DataGridColumnsModal-Panel',
16
+ })`
17
+ display: flex;
18
+ flex-direction: column;
19
+ flex: 1;
20
+ `;
21
+
22
+ export const Title = styled.div.attrs({
23
+ className: 'DataGridColumnsModal-Title',
24
+ })`
25
+ margin: 0;
26
+ margin-bottom: var(--space-2);
27
+ font-weight: bold;
28
+ `;
29
+
30
+ export const ColumnsList = styled.ul.attrs({
31
+ className: 'DataGridColumnsModal-ColumnsList',
32
+ })`
33
+ display: flex;
34
+ flex-direction: column;
35
+ flex: 1;
36
+ overflow: auto;
37
+ border: 1px solid var(--color-neutral-200);
38
+ margin: 0;
39
+ background-color: var(--color-neutral-50);
40
+
41
+ & > li:not(:last-child) {
42
+ border-bottom: 1px solid var(--color-neutral-100);
43
+ }
44
+ `;
45
+
46
+ export const ColumnItem = styled.li.attrs({
47
+ className: 'DataGridColumnsModal-ColumnItem',
48
+ })<{
49
+ $visible?: boolean;
50
+ }>`
51
+ padding: var(--space-1);
52
+ transition: background-color 0.2s;
53
+ cursor: pointer;
54
+ display: flex;
55
+ flex-direction: row;
56
+ align-items: center;
57
+ gap: var(--space-2);
58
+
59
+ span {
60
+ flex: 1;
61
+ }
62
+
63
+ button {
64
+ display: none;
65
+ padding: var(--space-1);
66
+ width: var(--space-6);
67
+ height: var(--space-6);
68
+ border-radius: var(--rounded-full);
69
+ background-color: var(--color-neutral-200);
70
+ border: none;
71
+ align-items: center;
72
+ justify-content: center;
73
+ }
74
+
75
+ &:hover {
76
+ background-color: var(--color-neutral-100);
77
+ button {
78
+ display: flex;
79
+ background-color: var(--color-neutral-300);
80
+ }
81
+ }
82
+ `;
83
+
84
+ export const Buttons = styled.div.attrs({
85
+ className: 'DataGridColumnsModal-Buttons',
86
+ })<{ $visible?: boolean }>`
87
+ display: flex;
88
+ gap: var(--space-1);
89
+ height: var(--space-6);
90
+ ${({ $visible }) =>
91
+ $visible
92
+ ? css`
93
+ width: var(--space-24);
94
+ justify-content: flex-end;
95
+ `
96
+ : css`
97
+ width: var(--space-6);
98
+ `}
99
+
100
+ svg {
101
+ width: var(--space-4);
102
+ height: var(--space-4);
103
+ }
104
+ `;
@@ -0,0 +1,54 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ /* eslint-disable @typescript-eslint/no-unnecessary-type-constraint */
3
+
4
+ import { useCallback, useState } from 'react';
5
+
6
+ import { DataGridCellProps } from './types';
7
+ import { join } from 'lodash';
8
+ import { useDataGridContext } from './hooks';
9
+
10
+ export const DataGridEditableCell = <R,>({
11
+ row,
12
+ columnKey,
13
+ column,
14
+ context,
15
+ }: DataGridCellProps<R>) => {
16
+ const { onCellEdited, setEditingCell } = useDataGridContext(context);
17
+ const [value, setValue] = useState(
18
+ column.getter
19
+ ? column.getter(row)
20
+ : column.propertyName
21
+ ? String(row[column.propertyName])
22
+ : ''
23
+ );
24
+
25
+ const onClose = useCallback(() => {
26
+ onCellEdited?.(row, columnKey, value);
27
+ setEditingCell([-1, -1]);
28
+ }, [columnKey, onCellEdited, row, setEditingCell, value]);
29
+
30
+ return (
31
+ <td
32
+ key={columnKey}
33
+ className={join(
34
+ [
35
+ // 'whitespace-nowrap py-1 text-sm',
36
+ column.className,
37
+ column.bodyClassName,
38
+ ],
39
+ ' '
40
+ )}
41
+ >
42
+ <input
43
+ type="text"
44
+ // className="relative z-10 block text-normal w-full border-0 py-1.5 text-gray-900 ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-sky-600 sm:text-sm sm:leading-6"
45
+ value={value}
46
+ onChange={(e) => setValue(e.target.value)}
47
+ />
48
+ <div
49
+ // className="fixed inset-0 bg-black bg-opacity-10"
50
+ onClick={onClose}
51
+ />
52
+ </td>
53
+ );
54
+ };
@@ -0,0 +1,75 @@
1
+ import { DataGridContext, DataGridFilter } from '../types';
2
+ import { FilterIcon, FilterSlashIcon } from '../../../../Icons';
3
+ import { useCallback, useState } from 'react';
4
+
5
+ import { Button } from '../../../forms';
6
+ import { FilterModalContent } from '../FilterModalContent';
7
+ import { Modal } from '../../../layout/Modal';
8
+ import { useDataGridContext } from '../hooks';
9
+
10
+ export const useFilterModal = <R,>({
11
+ columnKey,
12
+ context,
13
+ }: {
14
+ columnKey: string;
15
+ context: DataGridContext<R>;
16
+ }) => {
17
+ const [isVisible, setIsVisible] = useState(false);
18
+ const { filters = {}, columns, setFilters } = useDataGridContext<R>(context);
19
+ const column = columns[columnKey];
20
+ const [currentFilter, setCurrentFilter] = useState<DataGridFilter<R>>(
21
+ filters[columnKey] ?? column?.filter
22
+ );
23
+
24
+ const openModal = useCallback(() => {
25
+ setIsVisible(true);
26
+ }, []);
27
+ const closeModal = useCallback(() => {
28
+ setIsVisible(false);
29
+ }, []);
30
+
31
+ const onClearClicked = useCallback(() => {
32
+ const newFilters = { ...filters };
33
+ delete newFilters[columnKey];
34
+ setFilters(newFilters);
35
+ closeModal();
36
+ }, [closeModal, columnKey, filters, setFilters]);
37
+
38
+ const onApplyClicked = useCallback(() => {
39
+ setFilters({ ...filters, [columnKey]: currentFilter });
40
+ closeModal();
41
+ }, [closeModal, columnKey, currentFilter, filters, setFilters]);
42
+
43
+ const onCancelClicked = useCallback(() => {
44
+ closeModal();
45
+ }, [closeModal]);
46
+
47
+ const modal = isVisible ? (
48
+ <Modal>
49
+ <Modal.Header>Filtrer "{column.name}"</Modal.Header>
50
+ <Modal.ContentWithIcon>
51
+ <FilterIcon fill="var(--color-primary-400)" />
52
+ <FilterModalContent
53
+ filter={currentFilter}
54
+ onFilterChanged={setCurrentFilter}
55
+ />
56
+ </Modal.ContentWithIcon>
57
+ <Modal.Buttons>
58
+ <Button color="danger" icon={FilterSlashIcon} onClick={onClearClicked}>
59
+ Supprimer
60
+ </Button>
61
+ <Button
62
+ style={{ marginLeft: 'auto' }}
63
+ color="primary"
64
+ icon={FilterIcon}
65
+ onClick={onApplyClicked}
66
+ >
67
+ Filtrer
68
+ </Button>
69
+ <Button onClick={onCancelClicked}>Annuler</Button>
70
+ </Modal.Buttons>
71
+ </Modal>
72
+ ) : null;
73
+
74
+ return { openModal, closeModal, modal };
75
+ };
@@ -0,0 +1,190 @@
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 { FilterIcon, FilterSlashIcon, MagnifierIcon } from '../../../../Icons';
7
+ import { applyFilters, getDateGroups } from '../helpers';
8
+ import { intersection, uniq, without } from 'lodash';
9
+ import {
10
+ useCallback,
11
+ useContext,
12
+ useEffect,
13
+ useMemo,
14
+ useRef,
15
+ useState,
16
+ } from 'react';
17
+
18
+ import { DataGridContext } from '../types';
19
+ import { FilterValuesScroller } from '../FilterValuesScroller';
20
+ import { Input } from '../../../forms';
21
+ import { useFilterModal } from './hooks';
22
+
23
+ type FilterValuesProps<R> = {
24
+ columnKey: string;
25
+ columnIndex: number;
26
+ context: DataGridContext<R>;
27
+ onClose?: () => void;
28
+ };
29
+
30
+ export const DataGridFilterMenu = <R,>({
31
+ columnKey,
32
+ context,
33
+ onClose,
34
+ }: FilterValuesProps<R>) => {
35
+ const { openModal, modal } = useFilterModal({ columnKey, context });
36
+ const {
37
+ filters = {},
38
+ rows,
39
+ columns,
40
+ setFilters,
41
+ filterValuesLoader,
42
+ } = useContext(context);
43
+ const column = columns[columnKey] ?? {};
44
+ const textFilterInputRef = useRef<HTMLInputElement>(null);
45
+ const [textFilter, setTextFilter] = useState('');
46
+
47
+ const [availableValues, setAvailableValues] = useState<
48
+ (string | number | null)[]
49
+ >([]);
50
+
51
+ useEffect(() => {
52
+ if (filterValuesLoader) {
53
+ filterValuesLoader(columnKey).then((values) => {
54
+ setAvailableValues(() => values);
55
+ });
56
+ } else {
57
+ const otherFilters = Object.entries(filters)
58
+ .filter(([key]) => key !== columnKey)
59
+ .map(([, filter]) => filter);
60
+ const availableRows = applyFilters(rows, otherFilters);
61
+ const values = availableRows.map((row) => column.filter!.getter(row));
62
+ setAvailableValues(() => uniq(values).sort());
63
+ }
64
+ }, [column.filter, columnKey, filterValuesLoader, filters, rows]);
65
+
66
+ const selectedValues = useMemo(
67
+ () => filters?.[columnKey]?.values ?? [],
68
+ [columnKey, filters]
69
+ );
70
+
71
+ const clearFilter = useCallback(() => {
72
+ const newFilters = { ...filters };
73
+ delete newFilters[columnKey];
74
+ setFilters(newFilters);
75
+ onClose?.();
76
+ }, [filters, columnKey, setFilters, onClose]);
77
+
78
+ const setValuesChecked = useCallback(
79
+ (values: any[], checked?: boolean) => {
80
+ setFilters((prevFilters) => {
81
+ const newValues = checked
82
+ ? [...(prevFilters[columnKey]?.values ?? []), ...values]
83
+ : without(prevFilters[columnKey]?.values ?? [], ...values);
84
+ const newFilters = {
85
+ ...prevFilters,
86
+ };
87
+ if (newValues.length === 0) {
88
+ delete newFilters[columnKey];
89
+ } else {
90
+ newFilters[columnKey] = {
91
+ ...(prevFilters[columnKey] ?? column.filter),
92
+ operator: 'inArray',
93
+ values: newValues,
94
+ };
95
+ }
96
+ return newFilters;
97
+ });
98
+ },
99
+ [setFilters, columnKey, column.filter]
100
+ );
101
+
102
+ const toggleValues = useCallback(
103
+ (values: any[]) => {
104
+ const checked =
105
+ intersection(selectedValues, values).length === values.length;
106
+ setValuesChecked(values, !checked);
107
+ },
108
+ [setValuesChecked, selectedValues]
109
+ );
110
+
111
+ const formatter = useMemo(
112
+ () =>
113
+ column.filter?.formatter ?? ((v: any) => (v === null ? null : String(v))),
114
+ [column.filter?.formatter]
115
+ );
116
+
117
+ const filteredAvailableValues = useMemo(
118
+ () =>
119
+ !textFilter
120
+ ? availableValues
121
+ : availableValues.filter((value) =>
122
+ formatter(value)?.toLowerCase().includes(textFilter.toLowerCase())
123
+ ),
124
+ [availableValues, formatter, textFilter]
125
+ );
126
+
127
+ useEffect(() => {
128
+ if (textFilterInputRef.current) {
129
+ textFilterInputRef.current.focus();
130
+ }
131
+ }, []);
132
+
133
+ const checkboxesComponent = useMemo(() => {
134
+ if (column.type === 'date') {
135
+ const groups = getDateGroups(filteredAvailableValues);
136
+ return (
137
+ <FilterValuesScroller
138
+ values={filteredAvailableValues}
139
+ selectedValues={selectedValues}
140
+ onToggle={toggleValues}
141
+ formatter={formatter}
142
+ groups={groups}
143
+ />
144
+ );
145
+ }
146
+ return (
147
+ <FilterValuesScroller
148
+ values={filteredAvailableValues}
149
+ selectedValues={selectedValues}
150
+ onToggle={toggleValues}
151
+ formatter={formatter}
152
+ />
153
+ );
154
+ }, [
155
+ column.type,
156
+ filteredAvailableValues,
157
+ formatter,
158
+ selectedValues,
159
+ toggleValues,
160
+ ]);
161
+
162
+ return (
163
+ <styles.Menu onClick={(e) => e.stopPropagation()}>
164
+ {modal}
165
+ <styles.MenuItem onClick={openModal}>
166
+ <FilterIcon />
167
+ Filtrer ...
168
+ </styles.MenuItem>
169
+ <styles.MenuItem $color="danger" onClick={clearFilter}>
170
+ <FilterSlashIcon />
171
+ Supprimer le filtre
172
+ </styles.MenuItem>
173
+ <styles.InputContainer>
174
+ <MagnifierIcon />
175
+ <Input
176
+ ref={textFilterInputRef}
177
+ type="text"
178
+ name="search"
179
+ id="search"
180
+ placeholder="Rechercher ..."
181
+ value={textFilter}
182
+ onChange={(e) => setTextFilter(e.target.value)}
183
+ />
184
+ </styles.InputContainer>
185
+ <styles.CheckboxesContainer>
186
+ {checkboxesComponent}
187
+ </styles.CheckboxesContainer>
188
+ </styles.Menu>
189
+ );
190
+ };
@@ -0,0 +1,100 @@
1
+ import styled, { css } from 'styled-components';
2
+
3
+ import { ThemeColor } from '../../../../providers/ThemeProvider/types';
4
+
5
+ export const Menu = styled.div.attrs({
6
+ className: 'Menu',
7
+ })`
8
+ position: absolute;
9
+ inset: 0;
10
+ color: var(--color-neutral-900);
11
+ border-radius: var(--rounded-md);
12
+ padding: var(--space-1) 0;
13
+ box-shadow: var(--shadow-lg);
14
+ background-color: var(--color-neutral-100);
15
+ min-width: 20em;
16
+ outline: 1px solid var(--color-neutral-200);
17
+ display: flex;
18
+ flex-direction: column;
19
+ `;
20
+
21
+ export const MenuItem = styled.button.attrs({
22
+ className: 'MenuItem',
23
+ })<{ $color?: ThemeColor }>`
24
+ display: flex;
25
+ align-items: center;
26
+ width: 100%;
27
+ font-family: var(--font-sans);
28
+ font-weight: normal;
29
+ text-align: left;
30
+ padding: var(--space-1) var(--space-2);
31
+ font-size: var(--text-base);
32
+ line-height: var(--leading-6);
33
+ border: none;
34
+ cursor: pointer;
35
+
36
+ ${({ $color }) =>
37
+ $color
38
+ ? css`
39
+ color: var(--color-${$color}-600);
40
+ background-color: var(--color-neutral-100);
41
+ &:hover {
42
+ background-color: var(--color-${$color}-200);
43
+ }
44
+ `
45
+ : css`
46
+ color: var(--color-neutral-900);
47
+ background-color: var(--color-neutral-100);
48
+ &:hover {
49
+ background-color: var(--color-neutral-200);
50
+ }
51
+ `}
52
+
53
+ svg {
54
+ fill: currentColor;
55
+ width: var(--space-4);
56
+ height: var(--space-4);
57
+ margin-right: var(--space-2);
58
+ }
59
+ `;
60
+
61
+ export const InputContainer = styled.div.attrs({
62
+ className: 'InputContainer',
63
+ })`
64
+ position: relative;
65
+ border-radius: var(--rounded-md);
66
+ padding: 0 var(--space-2);
67
+ margin-top: var(--space-1);
68
+
69
+ svg {
70
+ position: absolute;
71
+ top: 50%;
72
+ transform: translateY(-50%);
73
+ left: var(--space-4);
74
+ display: flex;
75
+ align-items: center;
76
+ fill: var(--color-neutral-400);
77
+ width: var(--space-4);
78
+ height: var(--space-4);
79
+ }
80
+
81
+ input {
82
+ padding-left: var(--space-8);
83
+ }
84
+ `;
85
+
86
+ export const CheckboxesContainer = styled.div.attrs({
87
+ className: 'CheckboxesContainer',
88
+ })`
89
+ font-weight: normal;
90
+ user-select: none;
91
+ padding: var(--space-2);
92
+ margin: var(--space-2);
93
+ border: 1px solid var(--color-neutral-200);
94
+ border-radius: var(--rounded-md);
95
+ box-shadow: var(--shadow-inner);
96
+ background-color: var(--color-neutral-50);
97
+ height: 20em;
98
+ overflow-y: hidden;
99
+ white-space: nowrap;
100
+ `;