@addev-be/ui 0.3.5 → 0.3.6
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.
- package/package.json +1 -1
- package/src/components/data/DataGrid/DataGridRowTemplate.tsx +69 -0
- package/src/components/data/DataGrid/VirtualScroller.tsx +7 -6
- package/src/components/data/DataGrid/hooks/useDataGrid.tsx +14 -0
- package/src/components/data/DataGrid/index.tsx +2 -82
- package/src/components/data/DataGrid/types.ts +9 -0
- package/src/components/forms/styles.ts +3 -1
- package/src/components/search/QuickSearchBar.tsx +31 -29
- package/src/components/search/styles.ts +37 -18
- package/src/services/WebSocketService.ts +1 -0
package/package.json
CHANGED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import * as styles from './styles';
|
|
2
|
+
|
|
3
|
+
import { DataGridCell } from './DataGridCell';
|
|
4
|
+
import { DataGridRowTemplateProps } from './types';
|
|
5
|
+
import { useContext } from 'react';
|
|
6
|
+
|
|
7
|
+
export const DataGridRowTemplate = <R,>({
|
|
8
|
+
row,
|
|
9
|
+
rowIndex,
|
|
10
|
+
context,
|
|
11
|
+
}: DataGridRowTemplateProps<R>) => {
|
|
12
|
+
const { visibleColumns, rowKeyGetter, toggleSelection, ...props } =
|
|
13
|
+
useContext(context);
|
|
14
|
+
|
|
15
|
+
if (!row) {
|
|
16
|
+
return (
|
|
17
|
+
<styles.DataGridRow key={`loading-row-${rowIndex}`}>
|
|
18
|
+
{!!props.selectable && (
|
|
19
|
+
<styles.LoadingCell className="animate-pulse">
|
|
20
|
+
<div />
|
|
21
|
+
</styles.LoadingCell>
|
|
22
|
+
)}
|
|
23
|
+
{visibleColumns.map((_, index) => (
|
|
24
|
+
<styles.LoadingCell
|
|
25
|
+
className="animate-pulse"
|
|
26
|
+
key={`loading-${rowIndex}-${index}`}
|
|
27
|
+
>
|
|
28
|
+
<div />
|
|
29
|
+
</styles.LoadingCell>
|
|
30
|
+
))}
|
|
31
|
+
</styles.DataGridRow>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
const key = rowKeyGetter(row);
|
|
35
|
+
const selected = props.selectedKeys.includes(key);
|
|
36
|
+
const { className, style } = props.rowClassNameGetter?.(row) ?? {
|
|
37
|
+
className: '',
|
|
38
|
+
style: undefined,
|
|
39
|
+
};
|
|
40
|
+
return (
|
|
41
|
+
<styles.DataGridRow key={key}>
|
|
42
|
+
{!!props.selectable && (
|
|
43
|
+
<styles.SelectionCell
|
|
44
|
+
key="__select_checkbox__"
|
|
45
|
+
onClick={() => toggleSelection(key)}
|
|
46
|
+
>
|
|
47
|
+
<input
|
|
48
|
+
type="checkbox"
|
|
49
|
+
value={key as string}
|
|
50
|
+
checked={selected}
|
|
51
|
+
readOnly
|
|
52
|
+
/>
|
|
53
|
+
</styles.SelectionCell>
|
|
54
|
+
)}
|
|
55
|
+
{visibleColumns.map(([key, col], index) => (
|
|
56
|
+
<DataGridCell
|
|
57
|
+
key={`loading-${rowIndex}-${index}`}
|
|
58
|
+
{...(index === 0 ? { className, style } : {})}
|
|
59
|
+
row={row}
|
|
60
|
+
rowIndex={rowIndex}
|
|
61
|
+
column={col}
|
|
62
|
+
columnIndex={index}
|
|
63
|
+
context={context}
|
|
64
|
+
columnKey={key}
|
|
65
|
+
/>
|
|
66
|
+
))}
|
|
67
|
+
</styles.DataGridRow>
|
|
68
|
+
);
|
|
69
|
+
};
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import * as styles from './styles';
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
import { DataGridContext } from './types';
|
|
3
|
+
import { DataGridContext, DataGridRowTemplateProps } from './types';
|
|
4
|
+
import { FC, useContext } from 'react';
|
|
6
5
|
|
|
7
6
|
type VirtualScrollerProps<R> = {
|
|
8
7
|
showAllRows?: boolean;
|
|
9
|
-
rowTemplate:
|
|
8
|
+
rowTemplate: FC<DataGridRowTemplateProps<R>>;
|
|
10
9
|
hasFooter?: boolean;
|
|
11
10
|
context: DataGridContext<R>;
|
|
12
11
|
onRangeChange?: (startIndex: number, length: number) => void;
|
|
@@ -22,7 +21,7 @@ export const VirtualScroller = <R,>(props: VirtualScrollerProps<R>) => {
|
|
|
22
21
|
gridTemplateColumns,
|
|
23
22
|
} = useContext(props.context);
|
|
24
23
|
const {
|
|
25
|
-
rowTemplate,
|
|
24
|
+
rowTemplate: RowTemplate,
|
|
26
25
|
// hasFooter, onRangeChange
|
|
27
26
|
} = props;
|
|
28
27
|
|
|
@@ -39,7 +38,9 @@ export const VirtualScroller = <R,>(props: VirtualScrollerProps<R>) => {
|
|
|
39
38
|
$topPadding={topPadding}
|
|
40
39
|
$rowHeight={rowHeight}
|
|
41
40
|
>
|
|
42
|
-
{visibleRows.map(
|
|
41
|
+
{visibleRows.map((row, index) => (
|
|
42
|
+
<RowTemplate row={row} rowIndex={index} context={props.context} />
|
|
43
|
+
))}
|
|
43
44
|
</styles.VirtualScrollerRowsContainer>
|
|
44
45
|
</styles.VirtualScrollerContainer>
|
|
45
46
|
);
|
|
@@ -92,6 +92,17 @@ export const useDataGrid = <R,>(
|
|
|
92
92
|
onSelectionChange?.(selectedKeys);
|
|
93
93
|
}, [onSelectionChange, selectedKeys]);
|
|
94
94
|
|
|
95
|
+
const toggleSelection = useCallback(
|
|
96
|
+
(key: string) => {
|
|
97
|
+
if (selectedKeys.includes(key)) {
|
|
98
|
+
setSelectedKeys(selectedKeys.filter((p) => p !== key));
|
|
99
|
+
} else {
|
|
100
|
+
setSelectedKeys([...selectedKeys, key]);
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
[selectedKeys, setSelectedKeys]
|
|
104
|
+
);
|
|
105
|
+
|
|
95
106
|
/** ROWS FILTERING **/
|
|
96
107
|
|
|
97
108
|
const [filters, setFilters] = useState<DataGridFilters>({});
|
|
@@ -240,6 +251,7 @@ export const useDataGrid = <R,>(
|
|
|
240
251
|
footers,
|
|
241
252
|
setFooters,
|
|
242
253
|
footerFunctions,
|
|
254
|
+
toggleSelection,
|
|
243
255
|
...override,
|
|
244
256
|
}),
|
|
245
257
|
[
|
|
@@ -265,6 +277,7 @@ export const useDataGrid = <R,>(
|
|
|
265
277
|
gridTemplateColumns,
|
|
266
278
|
footers,
|
|
267
279
|
footerFunctions,
|
|
280
|
+
toggleSelection,
|
|
268
281
|
override,
|
|
269
282
|
]
|
|
270
283
|
);
|
|
@@ -298,6 +311,7 @@ export const useDataGrid = <R,>(
|
|
|
298
311
|
gridTemplateColumns: '',
|
|
299
312
|
footers: {},
|
|
300
313
|
setFooters: () => {},
|
|
314
|
+
toggleSelection: () => {},
|
|
301
315
|
}),
|
|
302
316
|
[]
|
|
303
317
|
);
|
|
@@ -2,11 +2,10 @@ import * as styles from './styles';
|
|
|
2
2
|
|
|
3
3
|
import { DataGridContextProps, DataGridProps } from './types';
|
|
4
4
|
|
|
5
|
-
import { DataGridCell } from './DataGridCell';
|
|
6
5
|
import { DataGridFooter } from './DataGridFooter';
|
|
7
6
|
import { DataGridHeader } from './DataGridHeader';
|
|
7
|
+
import { DataGridRowTemplate } from './DataGridRowTemplate';
|
|
8
8
|
import { VirtualScroller } from './VirtualScroller';
|
|
9
|
-
import { useCallback } from 'react';
|
|
10
9
|
import { useDataGrid } from './hooks';
|
|
11
10
|
|
|
12
11
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
@@ -25,95 +24,16 @@ export const DataGrid = <R,>({
|
|
|
25
24
|
} = props;
|
|
26
25
|
const [contextProps, DataGridContext] = useDataGrid(props, contextOverride);
|
|
27
26
|
const {
|
|
28
|
-
selectedKeys,
|
|
29
|
-
setSelectedKeys,
|
|
30
27
|
columns,
|
|
31
|
-
visibleColumns,
|
|
32
28
|
rowHeight = 32,
|
|
33
29
|
headerRowHeight = 40,
|
|
34
30
|
scrollableRef,
|
|
35
31
|
onScroll,
|
|
36
|
-
rowKeyGetter,
|
|
37
32
|
} = contextProps;
|
|
38
33
|
|
|
39
34
|
const hasFooter = Object.values(columns).some((col) => col.footer);
|
|
40
35
|
|
|
41
|
-
const
|
|
42
|
-
(key: string) => {
|
|
43
|
-
if (selectedKeys.includes(key)) {
|
|
44
|
-
setSelectedKeys(selectedKeys.filter((p) => p !== key));
|
|
45
|
-
} else {
|
|
46
|
-
setSelectedKeys([...selectedKeys, key]);
|
|
47
|
-
}
|
|
48
|
-
},
|
|
49
|
-
[selectedKeys, setSelectedKeys]
|
|
50
|
-
);
|
|
51
|
-
|
|
52
|
-
const rowTemplate = useCallback(
|
|
53
|
-
(row: R, rowIndex: number) => {
|
|
54
|
-
if (!row) {
|
|
55
|
-
return (
|
|
56
|
-
<styles.DataGridRow key={`loading-row-${rowIndex}`}>
|
|
57
|
-
{!!props.selectable && (
|
|
58
|
-
<styles.LoadingCell className="animate-pulse">
|
|
59
|
-
<div />
|
|
60
|
-
</styles.LoadingCell>
|
|
61
|
-
)}
|
|
62
|
-
{visibleColumns.map((_, index) => (
|
|
63
|
-
<styles.LoadingCell
|
|
64
|
-
className="animate-pulse"
|
|
65
|
-
key={`loading-${rowIndex}-${index}`}
|
|
66
|
-
>
|
|
67
|
-
<div />
|
|
68
|
-
</styles.LoadingCell>
|
|
69
|
-
))}
|
|
70
|
-
</styles.DataGridRow>
|
|
71
|
-
);
|
|
72
|
-
}
|
|
73
|
-
const key = rowKeyGetter(row);
|
|
74
|
-
const { className, style } = props.rowClassNameGetter?.(row) ?? {
|
|
75
|
-
className: '',
|
|
76
|
-
style: undefined,
|
|
77
|
-
};
|
|
78
|
-
return (
|
|
79
|
-
<styles.DataGridRow key={key}>
|
|
80
|
-
{!!props.selectable && (
|
|
81
|
-
<styles.SelectionCell
|
|
82
|
-
key="__select_checkbox__"
|
|
83
|
-
onClick={() => toggleSelection(key)}
|
|
84
|
-
>
|
|
85
|
-
<input
|
|
86
|
-
type="checkbox"
|
|
87
|
-
value={key as string}
|
|
88
|
-
checked={selectedKeys.includes(key)}
|
|
89
|
-
readOnly
|
|
90
|
-
/>
|
|
91
|
-
</styles.SelectionCell>
|
|
92
|
-
)}
|
|
93
|
-
{visibleColumns.map(([key, col], index) => (
|
|
94
|
-
<DataGridCell
|
|
95
|
-
key={`loading-${rowIndex}-${index}`}
|
|
96
|
-
{...(index === 0 ? { className, style } : {})}
|
|
97
|
-
row={row}
|
|
98
|
-
rowIndex={rowIndex}
|
|
99
|
-
column={col}
|
|
100
|
-
columnIndex={index}
|
|
101
|
-
context={DataGridContext}
|
|
102
|
-
columnKey={key}
|
|
103
|
-
/>
|
|
104
|
-
))}
|
|
105
|
-
</styles.DataGridRow>
|
|
106
|
-
);
|
|
107
|
-
},
|
|
108
|
-
[
|
|
109
|
-
DataGridContext,
|
|
110
|
-
props,
|
|
111
|
-
rowKeyGetter,
|
|
112
|
-
selectedKeys,
|
|
113
|
-
toggleSelection,
|
|
114
|
-
visibleColumns,
|
|
115
|
-
]
|
|
116
|
-
);
|
|
36
|
+
const rowTemplate = DataGridRowTemplate;
|
|
117
37
|
|
|
118
38
|
return (
|
|
119
39
|
<DataGridContext.Provider value={contextProps}>
|
|
@@ -131,6 +131,7 @@ export type DataGridContextProps<R> = DataGridProps<R> & {
|
|
|
131
131
|
length: number;
|
|
132
132
|
rowKeyGetter: (row: R) => string;
|
|
133
133
|
gridTemplateColumns: string;
|
|
134
|
+
toggleSelection: (key: string) => void;
|
|
134
135
|
};
|
|
135
136
|
|
|
136
137
|
export type DataGridContext<R> = Context<DataGridContextProps<R>>;
|
|
@@ -265,3 +266,11 @@ export type DataGridFilterCheckbox = {
|
|
|
265
266
|
values: DataGridFilterValue[];
|
|
266
267
|
level: number;
|
|
267
268
|
};
|
|
269
|
+
|
|
270
|
+
export type DataGridRowTemplateProps<R> = {
|
|
271
|
+
row: R | null;
|
|
272
|
+
rowIndex: number;
|
|
273
|
+
selected?: boolean;
|
|
274
|
+
toggleSelection?: () => void;
|
|
275
|
+
context: DataGridContext<R>;
|
|
276
|
+
};
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import * as styles from './styles';
|
|
2
|
+
|
|
1
3
|
import { SearchResults, SearchTypeDefinitions } from './types';
|
|
2
4
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
3
5
|
|
|
4
6
|
import { Dropdown } from '../layout';
|
|
5
|
-
import {
|
|
7
|
+
import { Input } from '../forms';
|
|
6
8
|
import { QuickSearchResults } from './QuickSearchResults';
|
|
7
9
|
import { useDebounce } from '@uidotdev/usehooks';
|
|
8
10
|
import { useGlobalSearchRequestHandler } from '../../services';
|
|
@@ -18,8 +20,10 @@ export const QuickSearchBar = <R,>({ definitions }: QuickSearchBarProps<R>) => {
|
|
|
18
20
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
19
21
|
const [results, setResults] = useState<SearchResults<R> | null>(null);
|
|
20
22
|
|
|
23
|
+
const fakeInputRef = useRef<HTMLInputElement | null>(null);
|
|
21
24
|
const inputRef = useRef<HTMLInputElement | null>(null);
|
|
22
|
-
const rect =
|
|
25
|
+
const rect = fakeInputRef.current?.getBoundingClientRect() ?? new DOMRect();
|
|
26
|
+
const destRect = new DOMRect(rect.x, rect.y, rect.width, 0);
|
|
23
27
|
const globalSearch = useGlobalSearchRequestHandler();
|
|
24
28
|
|
|
25
29
|
useEffect(() => {
|
|
@@ -36,41 +40,39 @@ export const QuickSearchBar = <R,>({ definitions }: QuickSearchBarProps<R>) => {
|
|
|
36
40
|
}, [globalSearch, debouncedTerm]);
|
|
37
41
|
|
|
38
42
|
const onFocus = useCallback(() => {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
useEffect(() => {
|
|
45
|
-
const input = inputRef.current;
|
|
46
|
-
input?.addEventListener('focus', onFocus);
|
|
47
|
-
return () => {
|
|
48
|
-
input?.removeEventListener('focus', onFocus);
|
|
49
|
-
};
|
|
50
|
-
}, [onFocus]);
|
|
43
|
+
setDropdownVisible(true);
|
|
44
|
+
requestAnimationFrame(() => {
|
|
45
|
+
inputRef.current?.focus();
|
|
46
|
+
});
|
|
47
|
+
}, []);
|
|
51
48
|
|
|
52
49
|
return (
|
|
53
50
|
<>
|
|
54
|
-
<
|
|
55
|
-
|
|
56
|
-
value={term}
|
|
57
|
-
onChange={(e) => setTerm(e.target.value)}
|
|
58
|
-
ref={inputRef}
|
|
59
|
-
$opened={dropdownVisible}
|
|
60
|
-
/>
|
|
61
|
-
{results && dropdownVisible && rect && (
|
|
51
|
+
<Input type="text" ref={fakeInputRef} value={term} onFocus={onFocus} />
|
|
52
|
+
{dropdownVisible && rect && (
|
|
62
53
|
<Dropdown
|
|
63
|
-
$sourceRect={
|
|
54
|
+
$sourceRect={destRect}
|
|
64
55
|
onClose={() => setDropdownVisible(false)}
|
|
65
56
|
$width={rect.width}
|
|
66
|
-
$height={[250, 400]}
|
|
57
|
+
$height={[results ? 250 : rect.height, 400]}
|
|
67
58
|
$autoPositionY={false}
|
|
68
59
|
>
|
|
69
|
-
<
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
60
|
+
<styles.QuickSearchDropdownContainer>
|
|
61
|
+
<Input
|
|
62
|
+
type="text"
|
|
63
|
+
ref={inputRef}
|
|
64
|
+
value={term}
|
|
65
|
+
onChange={(e) => setTerm(e.target.value)}
|
|
66
|
+
onClick={(e) => e.stopPropagation()}
|
|
67
|
+
/>
|
|
68
|
+
{results && (
|
|
69
|
+
<QuickSearchResults
|
|
70
|
+
results={results}
|
|
71
|
+
definitions={definitions}
|
|
72
|
+
term={debouncedTerm}
|
|
73
|
+
/>
|
|
74
|
+
)}
|
|
75
|
+
</styles.QuickSearchDropdownContainer>
|
|
74
76
|
</Dropdown>
|
|
75
77
|
)}
|
|
76
78
|
</>
|
|
@@ -1,41 +1,64 @@
|
|
|
1
|
-
import { Input } from '../forms';
|
|
2
1
|
import styled from 'styled-components';
|
|
3
2
|
|
|
4
|
-
export const
|
|
3
|
+
export const QuickSearchDropdownContainer = styled.div.attrs({
|
|
4
|
+
className: 'QuickSearchDropdownContainer',
|
|
5
|
+
})`
|
|
6
|
+
display: flex;
|
|
7
|
+
flex-direction: column;
|
|
8
|
+
height: 100%;
|
|
9
|
+
`;
|
|
10
|
+
|
|
11
|
+
export const QuickSearchResultsContainer = styled.div.attrs({
|
|
12
|
+
className: 'QuickSearchResultsContainer',
|
|
13
|
+
})`
|
|
5
14
|
display: flex;
|
|
6
15
|
flex-direction: row;
|
|
7
16
|
height: 100%;
|
|
17
|
+
flex: 1;
|
|
18
|
+
padding: var(--space-2) 0;
|
|
19
|
+
overflow: hidden;
|
|
8
20
|
`;
|
|
9
21
|
|
|
10
|
-
export const QuickSearchResultsListContainer = styled.div
|
|
22
|
+
export const QuickSearchResultsListContainer = styled.div.attrs({
|
|
23
|
+
className: 'QuickSearchResultsListContainer',
|
|
24
|
+
})`
|
|
11
25
|
display: flex;
|
|
12
26
|
flex-direction: column;
|
|
13
27
|
padding: var(--space-2);
|
|
14
|
-
border-right: 1px solid var(--color-
|
|
15
|
-
flex:
|
|
28
|
+
border-right: 1px solid var(--color-neutral-200);
|
|
29
|
+
flex: 1;
|
|
16
30
|
overflow: auto;
|
|
17
31
|
`;
|
|
18
32
|
|
|
19
|
-
export const QuickSearchResultsTitle = styled.div
|
|
33
|
+
export const QuickSearchResultsTitle = styled.div.attrs({
|
|
34
|
+
className: 'QuickSearchResultsTitle',
|
|
35
|
+
})`
|
|
20
36
|
margin: 0;
|
|
21
37
|
margin-bottom: var(--space-1);
|
|
22
38
|
&:not(:first-child) {
|
|
23
39
|
margin-top: var(--space-2);
|
|
24
40
|
}
|
|
25
41
|
font-weight: bold;
|
|
26
|
-
font-size: var(--text-
|
|
42
|
+
font-size: var(--text-sm);
|
|
43
|
+
text-transform: uppercase;
|
|
44
|
+
letter-spacing: 120%;
|
|
45
|
+
color: var(--color-neutral-500);
|
|
27
46
|
`;
|
|
28
47
|
|
|
29
|
-
export const QuickSearchResultsItem = styled.div
|
|
48
|
+
export const QuickSearchResultsItem = styled.div.attrs({
|
|
49
|
+
className: 'QuickSearchResultsItem',
|
|
50
|
+
})`
|
|
30
51
|
padding: var(--space-2) var(--space-3);
|
|
31
52
|
cursor: pointer;
|
|
32
53
|
border-radius: 4px;
|
|
33
54
|
&:hover {
|
|
34
|
-
background-color: var(--color-
|
|
55
|
+
background-color: var(--color-neutral-100);
|
|
35
56
|
}
|
|
36
57
|
`;
|
|
37
58
|
|
|
38
|
-
export const QuickSearchResultsDetailsContainer = styled.div
|
|
59
|
+
export const QuickSearchResultsDetailsContainer = styled.div.attrs({
|
|
60
|
+
className: 'QuickSearchResultsDetailsContainer',
|
|
61
|
+
})`
|
|
39
62
|
display: flex;
|
|
40
63
|
flex-direction: column;
|
|
41
64
|
padding: var(--space-2);
|
|
@@ -46,15 +69,11 @@ export const QuickSearchResultsDetailsDivider = styled.hr`
|
|
|
46
69
|
margin: var(--space-2) 0;
|
|
47
70
|
height: 1px;
|
|
48
71
|
border: none;
|
|
49
|
-
background-color: var(--color-
|
|
72
|
+
background-color: var(--color-neutral-200);
|
|
50
73
|
`;
|
|
51
74
|
|
|
52
|
-
export const QuickSearchResultDetailsTitle = styled.div
|
|
75
|
+
export const QuickSearchResultDetailsTitle = styled.div.attrs({
|
|
76
|
+
className: 'QuickSearchResultDetailsTitle',
|
|
77
|
+
})`
|
|
53
78
|
margin: 0;
|
|
54
79
|
`;
|
|
55
|
-
|
|
56
|
-
export const QuickSearchBarInput = styled(Input)<{ $opened?: boolean }>`
|
|
57
|
-
position: relative;
|
|
58
|
-
width: 100%;
|
|
59
|
-
z-index: ${({ $opened }) => ($opened ? 10000 : 0)};
|
|
60
|
-
`;
|