@linzjs/step-ag-grid 11.0.0 → 12.0.1
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/dist/index.css +10 -1
- package/dist/src/components/Grid.d.ts +0 -4
- package/dist/src/components/GridFilter.d.ts +2 -0
- package/dist/src/components/GridWrapper.d.ts +6 -0
- package/dist/src/components/gridFilter/GridFilterQuick.d.ts +6 -0
- package/dist/src/components/gridFilter/GridFilters.d.ts +5 -0
- package/dist/src/contexts/GridContext.d.ts +5 -0
- package/dist/src/index.d.ts +4 -0
- package/dist/step-ag-grid.esm.js +101 -44
- package/dist/step-ag-grid.esm.js.map +1 -1
- package/package.json +1 -1
- package/src/components/Grid.tsx +24 -49
- package/src/components/GridFilter.ts +12 -0
- package/src/components/GridWrapper.tsx +12 -0
- package/src/components/gridFilter/GridFilterQuick.tsx +29 -0
- package/src/components/gridFilter/GridFilters.tsx +7 -0
- package/src/contexts/GridContext.tsx +20 -0
- package/src/contexts/GridContextProvider.tsx +51 -17
- package/src/index.ts +5 -0
- package/src/stories/grid/GridPopoverEditBearing.stories.tsx +0 -1
- package/src/stories/grid/GridReadOnly.stories.tsx +46 -14
- package/src/stories/grid/gridFormInteraction/GridFormMultiSelectInteraction.stories.tsx +46 -13
- package/src/styles/Grid.scss +11 -1
package/package.json
CHANGED
package/src/components/Grid.tsx
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import clsx from "clsx";
|
|
2
2
|
import { useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
|
|
3
3
|
import { AgGridReact } from "ag-grid-react";
|
|
4
|
-
import { CellClickedEvent, ColDef } from "ag-grid-community";
|
|
5
|
-
import { CellEvent, GridReadyEvent, SelectionChangedEvent } from "ag-grid-community/dist/lib/events";
|
|
4
|
+
import { CellClickedEvent, ColDef, ModelUpdatedEvent } from "ag-grid-community";
|
|
5
|
+
import { AgGridEvent, CellEvent, GridReadyEvent, SelectionChangedEvent } from "ag-grid-community/dist/lib/events";
|
|
6
6
|
import { GridOptions } from "ag-grid-community/dist/lib/entities/gridOptions";
|
|
7
7
|
import { difference, isEmpty, last, xorBy } from "lodash-es";
|
|
8
8
|
import { GridContext } from "../contexts/GridContext";
|
|
@@ -20,16 +20,12 @@ export interface GridProps {
|
|
|
20
20
|
readOnly?: boolean; // set all editables to false when read only, make all styles black, otherwise style is gray for not editable
|
|
21
21
|
selectable?: boolean;
|
|
22
22
|
["data-testid"]?: string;
|
|
23
|
-
quickFilter?: boolean;
|
|
24
|
-
quickFilterPlaceholder?: string;
|
|
25
|
-
quickFilterValue?: string;
|
|
26
23
|
domLayout?: GridOptions["domLayout"];
|
|
27
24
|
externalSelectedItems?: any[];
|
|
28
25
|
setExternalSelectedItems?: (items: any[]) => void;
|
|
29
26
|
defaultColDef?: GridOptions["defaultColDef"];
|
|
30
27
|
columnDefs: ColDef[];
|
|
31
28
|
rowData: GridOptions["rowData"];
|
|
32
|
-
noRowsOverlayText?: string;
|
|
33
29
|
postSortRows?: GridOptions["postSortRows"];
|
|
34
30
|
animateRows?: boolean;
|
|
35
31
|
rowClassRules?: GridOptions["rowClassRules"];
|
|
@@ -47,7 +43,6 @@ export const Grid = (params: GridProps): JSX.Element => {
|
|
|
47
43
|
gridReady,
|
|
48
44
|
setGridApi,
|
|
49
45
|
prePopupOps,
|
|
50
|
-
setQuickFilter,
|
|
51
46
|
ensureRowVisible,
|
|
52
47
|
selectRowsById,
|
|
53
48
|
focusByRowById,
|
|
@@ -55,10 +50,11 @@ export const Grid = (params: GridProps): JSX.Element => {
|
|
|
55
50
|
sizeColumnsToFit,
|
|
56
51
|
externallySelectedItemsAreInSync,
|
|
57
52
|
setExternallySelectedItemsAreInSync,
|
|
53
|
+
isExternalFilterPresent,
|
|
54
|
+
doesExternalFilterPass,
|
|
58
55
|
} = useContext(GridContext);
|
|
59
56
|
const { checkUpdating } = useContext(GridUpdatingContext);
|
|
60
57
|
|
|
61
|
-
const [internalQuickFilter, setInternalQuickFilter] = useState("");
|
|
62
58
|
const lastSelectedIds = useRef<number[]>([]);
|
|
63
59
|
const [staleGrid, setStaleGrid] = useState(false);
|
|
64
60
|
const postSortRows = usePostSortRowsHook({ setStaleGrid });
|
|
@@ -142,26 +138,6 @@ export const Grid = (params: GridProps): JSX.Element => {
|
|
|
142
138
|
setExternallySelectedItemsAreInSync(true);
|
|
143
139
|
}, [gridReady, params.externalSelectedItems, ensureRowVisible, selectRowsById, setExternallySelectedItemsAreInSync]);
|
|
144
140
|
|
|
145
|
-
/**
|
|
146
|
-
* Synchronise quick filter to grid
|
|
147
|
-
*/
|
|
148
|
-
const updateQuickFilter = useCallback(() => {
|
|
149
|
-
if (!gridReady) return;
|
|
150
|
-
if (params.quickFilter) {
|
|
151
|
-
setQuickFilter(internalQuickFilter);
|
|
152
|
-
return;
|
|
153
|
-
}
|
|
154
|
-
if (params.quickFilterValue == null) return;
|
|
155
|
-
setQuickFilter(params.quickFilterValue);
|
|
156
|
-
}, [gridReady, internalQuickFilter, params.quickFilter, params.quickFilterValue, setQuickFilter]);
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* Synchronise quick filter to grid
|
|
160
|
-
*/
|
|
161
|
-
useEffect(() => {
|
|
162
|
-
updateQuickFilter();
|
|
163
|
-
}, [updateQuickFilter]);
|
|
164
|
-
|
|
165
141
|
const combineEditables =
|
|
166
142
|
(...editables: (boolean | EditableCallback | undefined)[]) =>
|
|
167
143
|
(params: EditableCallbackParams): boolean => {
|
|
@@ -201,7 +177,7 @@ export const Grid = (params: GridProps): JSX.Element => {
|
|
|
201
177
|
maxWidth: 42,
|
|
202
178
|
suppressSizeToFit: true,
|
|
203
179
|
checkboxSelection: true,
|
|
204
|
-
headerComponent: GridHeaderSelect,
|
|
180
|
+
headerComponent: params.rowSelection === "multiple" ? GridHeaderSelect : null,
|
|
205
181
|
suppressHeaderKeyboardEvent: (e) => {
|
|
206
182
|
if ((e.event.key === "Enter" || e.event.key === " ") && !e.event.repeat) {
|
|
207
183
|
const selectedNodeCount = e.api.getSelectedRows().length;
|
|
@@ -220,27 +196,37 @@ export const Grid = (params: GridProps): JSX.Element => {
|
|
|
220
196
|
]
|
|
221
197
|
: adjustColDefs;
|
|
222
198
|
}, [
|
|
223
|
-
clickSelectorCheckboxWhenContainingCellClicked,
|
|
224
199
|
params.columnDefs,
|
|
225
200
|
params.selectable,
|
|
201
|
+
params.rowSelection,
|
|
226
202
|
params.readOnly,
|
|
227
|
-
params.defaultColDef,
|
|
203
|
+
params.defaultColDef?.editable,
|
|
204
|
+
clickSelectorCheckboxWhenContainingCellClicked,
|
|
228
205
|
]);
|
|
229
206
|
|
|
230
207
|
const onGridReady = useCallback(
|
|
231
208
|
(event: GridReadyEvent) => {
|
|
232
209
|
setGridApi(event.api);
|
|
233
210
|
synchroniseExternallySelectedItemsToGrid();
|
|
234
|
-
updateQuickFilter();
|
|
235
211
|
},
|
|
236
|
-
[setGridApi, synchroniseExternallySelectedItemsToGrid
|
|
212
|
+
[setGridApi, synchroniseExternallySelectedItemsToGrid],
|
|
237
213
|
);
|
|
238
214
|
|
|
239
215
|
const noRowsOverlayComponent = useCallback(
|
|
240
|
-
() =>
|
|
241
|
-
|
|
216
|
+
(event: AgGridEvent) => {
|
|
217
|
+
const hasData = (params.rowData?.length ?? 0) > 0;
|
|
218
|
+
const hasFilteredData = event.api.getDisplayedRowCount() > 0;
|
|
219
|
+
return (
|
|
220
|
+
<span>{!hasData ? "There are currently no rows" : !hasFilteredData ? "All rows have been filtered" : ""}</span>
|
|
221
|
+
);
|
|
222
|
+
},
|
|
223
|
+
[params.rowData?.length],
|
|
242
224
|
);
|
|
243
225
|
|
|
226
|
+
const onModelUpdated = useCallback((event: ModelUpdatedEvent) => {
|
|
227
|
+
event.api.getDisplayedRowCount() === 0 ? event.api.showNoRowsOverlay() : event.api.hideOverlay();
|
|
228
|
+
}, []);
|
|
229
|
+
|
|
244
230
|
const refreshSelectedRows = useCallback((event: CellEvent): void => {
|
|
245
231
|
// Force-refresh all selected rows to re-run class function, to update selection highlighting
|
|
246
232
|
event.api.refreshCells({
|
|
@@ -326,20 +312,6 @@ export const Grid = (params: GridProps): JSX.Element => {
|
|
|
326
312
|
gridReady && params.rowData && "Grid-ready",
|
|
327
313
|
)}
|
|
328
314
|
>
|
|
329
|
-
{params.quickFilter && (
|
|
330
|
-
<div className="Grid-quickFilter">
|
|
331
|
-
<input
|
|
332
|
-
aria-label="Search"
|
|
333
|
-
className="lui-margin-top-xxs lui-margin-bottom-xxs Grid-quickFilterBox"
|
|
334
|
-
type="text"
|
|
335
|
-
placeholder={params.quickFilterPlaceholder ?? "Search..."}
|
|
336
|
-
value={internalQuickFilter}
|
|
337
|
-
onChange={(event): void => {
|
|
338
|
-
setInternalQuickFilter(event.target.value);
|
|
339
|
-
}}
|
|
340
|
-
/>
|
|
341
|
-
</div>
|
|
342
|
-
)}
|
|
343
315
|
<div style={{ flex: 1 }}>
|
|
344
316
|
<AgGridReact
|
|
345
317
|
animateRows={params.animateRows}
|
|
@@ -360,12 +332,15 @@ export const Grid = (params: GridProps): JSX.Element => {
|
|
|
360
332
|
columnDefs={columnDefs}
|
|
361
333
|
rowData={params.rowData}
|
|
362
334
|
noRowsOverlayComponent={noRowsOverlayComponent}
|
|
335
|
+
onModelUpdated={onModelUpdated}
|
|
363
336
|
onGridReady={onGridReady}
|
|
364
337
|
onSortChanged={ensureSelectedRowIsVisible}
|
|
365
338
|
postSortRows={params.postSortRows ?? postSortRows}
|
|
366
339
|
onSelectionChanged={synchroniseExternalStateToGridSelection}
|
|
367
340
|
onColumnMoved={params.onColumnMoved}
|
|
368
341
|
alwaysShowVerticalScroll={params.alwaysShowVerticalScroll}
|
|
342
|
+
isExternalFilterPresent={isExternalFilterPresent}
|
|
343
|
+
doesExternalFilterPass={doesExternalFilterPass}
|
|
369
344
|
/>
|
|
370
345
|
</div>
|
|
371
346
|
</div>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { useContext, useEffect } from "react";
|
|
2
|
+
import { GridContext, GridFilterExternal } from "../contexts/GridContext";
|
|
3
|
+
|
|
4
|
+
export const useGridFilter = (filter: GridFilterExternal) => {
|
|
5
|
+
const { addExternalFilter, removeExternalFilter } = useContext(GridContext);
|
|
6
|
+
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
const thisFilter = filter;
|
|
9
|
+
addExternalFilter(thisFilter);
|
|
10
|
+
return () => removeExternalFilter(thisFilter);
|
|
11
|
+
}, [addExternalFilter, filter, removeExternalFilter]);
|
|
12
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
export interface GridWrapperProps {
|
|
4
|
+
children: ReactNode;
|
|
5
|
+
maxHeight?: number | string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const GridWrapper = ({ children, maxHeight }: GridWrapperProps) => (
|
|
9
|
+
<div className={"Grid-wrapper"} style={{ maxHeight }}>
|
|
10
|
+
{children}
|
|
11
|
+
</div>
|
|
12
|
+
);
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { useContext, useEffect, useState } from "react";
|
|
2
|
+
import { GridContext } from "../../contexts/GridContext";
|
|
3
|
+
|
|
4
|
+
export interface GridFilterQuickProps {
|
|
5
|
+
quickFilterPlaceholder?: string;
|
|
6
|
+
defaultValue?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const GridFilterQuick = ({ quickFilterPlaceholder, defaultValue }: GridFilterQuickProps) => {
|
|
10
|
+
const { setQuickFilter } = useContext(GridContext);
|
|
11
|
+
const [quickFilterValue, setQuickFilterValue] = useState(defaultValue ?? "");
|
|
12
|
+
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
setQuickFilter(quickFilterValue);
|
|
15
|
+
}, [quickFilterValue, setQuickFilter]);
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<input
|
|
19
|
+
aria-label="Search"
|
|
20
|
+
className="lui-margin-top-xxs lui-margin-bottom-xxs Grid-quickFilterBox"
|
|
21
|
+
type="text"
|
|
22
|
+
placeholder={quickFilterPlaceholder ?? "Search..."}
|
|
23
|
+
value={quickFilterValue}
|
|
24
|
+
onChange={(event): void => {
|
|
25
|
+
setQuickFilterValue(event.target.value);
|
|
26
|
+
}}
|
|
27
|
+
/>
|
|
28
|
+
);
|
|
29
|
+
};
|
|
@@ -2,6 +2,8 @@ import { createContext } from "react";
|
|
|
2
2
|
import { GridApi, RowNode } from "ag-grid-community";
|
|
3
3
|
import { GridBaseRow } from "../components/Grid";
|
|
4
4
|
|
|
5
|
+
export type GridFilterExternal = (data: any, rowNode: RowNode) => boolean;
|
|
6
|
+
|
|
5
7
|
export interface GridContextType {
|
|
6
8
|
gridReady: boolean;
|
|
7
9
|
setGridApi: (gridApi: GridApi | undefined) => void;
|
|
@@ -31,6 +33,10 @@ export interface GridContextType {
|
|
|
31
33
|
externallySelectedItemsAreInSync: boolean;
|
|
32
34
|
setExternallySelectedItemsAreInSync: (inSync: boolean) => void;
|
|
33
35
|
waitForExternallySelectedItemsToBeInSync: () => Promise<void>;
|
|
36
|
+
addExternalFilter: (filter: GridFilterExternal) => void;
|
|
37
|
+
removeExternalFilter: (filter: GridFilterExternal) => void;
|
|
38
|
+
isExternalFilterPresent: () => boolean;
|
|
39
|
+
doesExternalFilterPass: (node: RowNode) => boolean;
|
|
34
40
|
}
|
|
35
41
|
|
|
36
42
|
export const GridContext = createContext<GridContextType>({
|
|
@@ -104,4 +110,18 @@ export const GridContext = createContext<GridContextType>({
|
|
|
104
110
|
waitForExternallySelectedItemsToBeInSync: async () => {
|
|
105
111
|
console.error("no context provider for waitForExternallySelectedItemsToBeInSync");
|
|
106
112
|
},
|
|
113
|
+
addExternalFilter: () => {
|
|
114
|
+
console.error("no context provider for addExternalFilter");
|
|
115
|
+
},
|
|
116
|
+
removeExternalFilter: () => {
|
|
117
|
+
console.error("no context provider for removeExternalFilter");
|
|
118
|
+
},
|
|
119
|
+
isExternalFilterPresent: () => {
|
|
120
|
+
console.error("no context provider for isExternalFilterPresent");
|
|
121
|
+
return false;
|
|
122
|
+
},
|
|
123
|
+
doesExternalFilterPass: () => {
|
|
124
|
+
console.error("no context provider for doesExternalFilterPass");
|
|
125
|
+
return true;
|
|
126
|
+
},
|
|
107
127
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { ReactElement, ReactNode, useCallback, useContext, useRef, useState } from "react";
|
|
1
|
+
import { ReactElement, ReactNode, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
|
|
2
2
|
import { ColDef, GridApi, RowNode } from "ag-grid-community";
|
|
3
|
-
import { GridContext } from "./GridContext";
|
|
4
|
-
import { defer, delay, difference, isEmpty, last, sortBy } from "lodash-es";
|
|
3
|
+
import { GridContext, GridFilterExternal } from "./GridContext";
|
|
4
|
+
import { debounce, defer, delay, difference, isEmpty, last, remove, sortBy } from "lodash-es";
|
|
5
5
|
import { isNotEmpty, wait } from "../utils/util";
|
|
6
6
|
import { GridUpdatingContext } from "./GridUpdatingContext";
|
|
7
7
|
import { GridBaseRow } from "../components/Grid";
|
|
@@ -20,14 +20,30 @@ export const GridContextProvider = (props: GridContextProps): ReactElement => {
|
|
|
20
20
|
const { modifyUpdating } = useContext(GridUpdatingContext);
|
|
21
21
|
const [gridApi, _setGridApi] = useState<GridApi>();
|
|
22
22
|
const [gridReady, setGridReady] = useState(false);
|
|
23
|
+
const [quickFilter, setQuickFilter] = useState("");
|
|
23
24
|
const idsBeforeUpdate = useRef<number[]>([]);
|
|
24
25
|
const prePopupFocusedCell = useRef<CellPosition>();
|
|
25
26
|
const [externallySelectedItemsAreInSync, setExternallySelectedItemsAreInSync] = useState(false);
|
|
27
|
+
const externalFilters = useRef<GridFilterExternal[]>([]);
|
|
26
28
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Set quick filter directly on grid, based on previously save quickFilter state.
|
|
31
|
+
*/
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
gridApi?.setQuickFilter(quickFilter);
|
|
34
|
+
}, [gridApi, quickFilter]);
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Set the grid api when the grid is ready.
|
|
38
|
+
*/
|
|
39
|
+
const setGridApi = useCallback(
|
|
40
|
+
(gridApi: GridApi | undefined) => {
|
|
41
|
+
_setGridApi(gridApi);
|
|
42
|
+
gridApi?.setQuickFilter(quickFilter);
|
|
43
|
+
setGridReady(!!gridApi);
|
|
44
|
+
},
|
|
45
|
+
[quickFilter],
|
|
46
|
+
);
|
|
31
47
|
|
|
32
48
|
/**
|
|
33
49
|
* Wraps things that require gridApi in common handling, for when gridApi not present.
|
|
@@ -52,16 +68,6 @@ export const GridContextProvider = (props: GridContextProps): ReactElement => {
|
|
|
52
68
|
prePopupFocusedCell.current = gridApi?.getFocusedCell() ?? undefined;
|
|
53
69
|
}, [gridApi]);
|
|
54
70
|
|
|
55
|
-
/**
|
|
56
|
-
* Set the quick filter value to grid.
|
|
57
|
-
*/
|
|
58
|
-
const setQuickFilter = useCallback(
|
|
59
|
-
(quickFilter: string) => {
|
|
60
|
-
gridApiOp((gridApi) => gridApi.setQuickFilter(quickFilter));
|
|
61
|
-
},
|
|
62
|
-
[gridApiOp],
|
|
63
|
-
);
|
|
64
|
-
|
|
65
71
|
/**
|
|
66
72
|
* Get all row id's in grid.
|
|
67
73
|
*/
|
|
@@ -376,6 +382,30 @@ export const GridContextProvider = (props: GridContextProps): ReactElement => {
|
|
|
376
382
|
}
|
|
377
383
|
}, [externallySelectedItemsAreInSync]);
|
|
378
384
|
|
|
385
|
+
const onFilterChanged = useMemo(
|
|
386
|
+
() =>
|
|
387
|
+
debounce(() => {
|
|
388
|
+
gridApi && gridApi.onFilterChanged();
|
|
389
|
+
}, 200),
|
|
390
|
+
[gridApi],
|
|
391
|
+
);
|
|
392
|
+
|
|
393
|
+
const addExternalFilter = (filter: GridFilterExternal) => {
|
|
394
|
+
externalFilters.current.push(filter);
|
|
395
|
+
onFilterChanged();
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
const removeExternalFilter = (filter: GridFilterExternal) => {
|
|
399
|
+
remove(externalFilters.current, (v) => v === filter);
|
|
400
|
+
onFilterChanged();
|
|
401
|
+
};
|
|
402
|
+
|
|
403
|
+
const isExternalFilterPresent = (): boolean => externalFilters.current.length > 0;
|
|
404
|
+
|
|
405
|
+
const doesExternalFilterPass = (node: RowNode): boolean => {
|
|
406
|
+
return externalFilters.current.every((filter) => filter(node.data, node));
|
|
407
|
+
};
|
|
408
|
+
|
|
379
409
|
return (
|
|
380
410
|
<GridContext.Provider
|
|
381
411
|
value={{
|
|
@@ -402,6 +432,10 @@ export const GridContextProvider = (props: GridContextProps): ReactElement => {
|
|
|
402
432
|
externallySelectedItemsAreInSync,
|
|
403
433
|
setExternallySelectedItemsAreInSync,
|
|
404
434
|
waitForExternallySelectedItemsToBeInSync,
|
|
435
|
+
addExternalFilter,
|
|
436
|
+
removeExternalFilter,
|
|
437
|
+
isExternalFilterPresent,
|
|
438
|
+
doesExternalFilterPass,
|
|
405
439
|
}}
|
|
406
440
|
>
|
|
407
441
|
{props.children}
|
package/src/index.ts
CHANGED
|
@@ -41,6 +41,11 @@ export * from "./components/gridForm/GridFormTextArea";
|
|
|
41
41
|
export * from "./components/gridForm/GridFormMessage";
|
|
42
42
|
export * from "./components/gridForm/GridFormEditBearing";
|
|
43
43
|
|
|
44
|
+
export { useGridFilter } from "./components/GridFilter";
|
|
45
|
+
export * from "./components/gridFilter/GridFilterQuick";
|
|
46
|
+
export * from "./components/gridFilter/GridFilters";
|
|
47
|
+
export * from "./components/GridWrapper";
|
|
48
|
+
|
|
44
49
|
export { GridHeaderSelect } from "./components/gridHeader/GridHeaderSelect";
|
|
45
50
|
|
|
46
51
|
export { TextAreaInput } from "./lui/TextAreaInput";
|
|
@@ -7,15 +7,18 @@ import { ComponentMeta, ComponentStory } from "@storybook/react/dist/ts3.9/clien
|
|
|
7
7
|
import { GridUpdatingContextProvider } from "../../contexts/GridUpdatingContextProvider";
|
|
8
8
|
import { GridContextProvider } from "../../contexts/GridContextProvider";
|
|
9
9
|
import { Grid, GridProps } from "../../components/Grid";
|
|
10
|
-
import { useMemo, useState } from "react";
|
|
10
|
+
import { useCallback, useMemo, useState } from "react";
|
|
11
11
|
import { wait } from "../../utils/util";
|
|
12
12
|
import { GridPopoverMenu } from "../../components/gridPopoverEdit/GridPopoverMenu";
|
|
13
13
|
import { ColDefT, GridCell } from "../../components/GridCell";
|
|
14
14
|
import { GridPopoverMessage } from "../../components/gridPopoverEdit/GridPopoverMessage";
|
|
15
|
-
import { MenuOption } from "../../components/gridForm/GridFormPopoverMenu";
|
|
16
15
|
import { GridFormSubComponentTextInput } from "../../components/gridForm/GridFormSubComponentTextInput";
|
|
17
16
|
import { GridFormSubComponentTextArea } from "../../components/gridForm/GridFormSubComponentTextArea";
|
|
18
17
|
import { GridIcon } from "../../components/GridIcon";
|
|
18
|
+
import { useGridFilter } from "../../components/GridFilter";
|
|
19
|
+
import { GridFilterQuick } from "../../components/gridFilter/GridFilterQuick";
|
|
20
|
+
import { GridFilters } from "../../components/gridFilter/GridFilters";
|
|
21
|
+
import { GridWrapper } from "../../components/GridWrapper";
|
|
19
22
|
|
|
20
23
|
export default {
|
|
21
24
|
title: "Components / Grids",
|
|
@@ -29,7 +32,7 @@ export default {
|
|
|
29
32
|
},
|
|
30
33
|
decorators: [
|
|
31
34
|
(Story) => (
|
|
32
|
-
<div style={{ width: 1024, height: 400 }}>
|
|
35
|
+
<div style={{ width: 1024, height: 400, display: "flex", flexDirection: "column" }}>
|
|
33
36
|
<GridUpdatingContextProvider>
|
|
34
37
|
<GridContextProvider>
|
|
35
38
|
<Story />
|
|
@@ -173,7 +176,7 @@ const GridReadOnlyTemplate: ComponentStory<typeof Grid> = (props: GridProps) =>
|
|
|
173
176
|
<GridFormSubComponentTextArea placeholder={"Other"} maxLength={5} required defaultValue={""} />
|
|
174
177
|
),
|
|
175
178
|
},
|
|
176
|
-
]
|
|
179
|
+
];
|
|
177
180
|
},
|
|
178
181
|
},
|
|
179
182
|
},
|
|
@@ -201,17 +204,46 @@ const GridReadOnlyTemplate: ComponentStory<typeof Grid> = (props: GridProps) =>
|
|
|
201
204
|
]);
|
|
202
205
|
|
|
203
206
|
return (
|
|
204
|
-
<
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
207
|
+
<GridWrapper maxHeight={200}>
|
|
208
|
+
<GridFilters>
|
|
209
|
+
<GridFilterQuick quickFilterPlaceholder={"Custom placeholder..."} />
|
|
210
|
+
<div>
|
|
211
|
+
Custom filter: Age less than:
|
|
212
|
+
<GridFilterLessThan field={"age"} />
|
|
213
|
+
</div>
|
|
214
|
+
</GridFilters>
|
|
215
|
+
<Grid
|
|
216
|
+
{...props}
|
|
217
|
+
selectable={true}
|
|
218
|
+
autoSelectFirstRow={true}
|
|
219
|
+
externalSelectedItems={externalSelectedItems}
|
|
220
|
+
setExternalSelectedItems={setExternalSelectedItems}
|
|
221
|
+
columnDefs={columnDefs}
|
|
222
|
+
rowData={rowData}
|
|
223
|
+
/>
|
|
224
|
+
</GridWrapper>
|
|
214
225
|
);
|
|
215
226
|
};
|
|
216
227
|
|
|
228
|
+
const GridFilterLessThan = (props: { field: keyof ITestRow }): JSX.Element => {
|
|
229
|
+
const [value, setValue] = useState<number>();
|
|
230
|
+
|
|
231
|
+
const filter = useCallback(
|
|
232
|
+
(data: ITestRow): boolean => value == null || data[props.field] < value,
|
|
233
|
+
[props.field, value],
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
useGridFilter(filter);
|
|
237
|
+
|
|
238
|
+
const updateValue = (newValue: string) => {
|
|
239
|
+
try {
|
|
240
|
+
setValue(newValue.trim() == "" ? undefined : parseInt(newValue));
|
|
241
|
+
} catch {
|
|
242
|
+
// ignore number parse exception
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
return <input type={"text"} defaultValue={value} onChange={(e) => updateValue(e.target.value)} />;
|
|
247
|
+
};
|
|
248
|
+
|
|
217
249
|
export const ReadOnlySingleSelection = GridReadOnlyTemplate.bind({});
|
|
@@ -27,23 +27,26 @@ const updateValue = jest
|
|
|
27
27
|
.mockImplementation((saveFn: (selectedRows: any[]) => Promise<boolean>, _tabDirection: 1 | 0 | -1) => saveFn([]));
|
|
28
28
|
|
|
29
29
|
const onSave = jest.fn<Promise<boolean>, [GridFormMultiSelectSaveProps<any>]>().mockImplementation(async () => true);
|
|
30
|
+
const onSelectFilter = jest.fn();
|
|
30
31
|
|
|
31
|
-
|
|
32
|
-
{ label: "Zero", value: 0 },
|
|
33
|
-
{ label: "One", value: 1 },
|
|
34
|
-
{
|
|
35
|
-
label: "Sub component",
|
|
36
|
-
value: 2,
|
|
37
|
-
subComponent: () => (
|
|
38
|
-
<GridFormSubComponentTextInput placeholder={"Text input"} maxLength={5} required defaultValue={""} />
|
|
39
|
-
),
|
|
40
|
-
},
|
|
41
|
-
{ label: "Other", value: 3 },
|
|
42
|
-
]) as MultiSelectOption[];
|
|
43
|
-
|
|
32
|
+
let options: MultiSelectOption[] = [];
|
|
44
33
|
const Template: ComponentStory<typeof GridFormMultiSelect> = (props) => {
|
|
34
|
+
options = [
|
|
35
|
+
{ label: "Zero", value: 0 },
|
|
36
|
+
{ label: "One", value: 1 },
|
|
37
|
+
{
|
|
38
|
+
label: "Sub component",
|
|
39
|
+
value: 2,
|
|
40
|
+
subComponent: () => (
|
|
41
|
+
<GridFormSubComponentTextInput placeholder={"Text input"} maxLength={5} required defaultValue={""} />
|
|
42
|
+
),
|
|
43
|
+
},
|
|
44
|
+
{ label: "Other", value: 3 },
|
|
45
|
+
];
|
|
45
46
|
const config: GridFormMultiSelectProps<any> = {
|
|
46
47
|
filtered: true,
|
|
48
|
+
onSelectFilter,
|
|
49
|
+
filterHelpText: "Press enter to add free-text",
|
|
47
50
|
onSave,
|
|
48
51
|
options,
|
|
49
52
|
};
|
|
@@ -76,6 +79,10 @@ const Template: ComponentStory<typeof GridFormMultiSelect> = (props) => {
|
|
|
76
79
|
|
|
77
80
|
export const GridFormMultiSelectInteractions_ = Template.bind({});
|
|
78
81
|
GridFormMultiSelectInteractions_.play = async ({ canvasElement }) => {
|
|
82
|
+
updateValue.mockClear();
|
|
83
|
+
onSave.mockClear();
|
|
84
|
+
onSelectFilter.mockClear();
|
|
85
|
+
|
|
79
86
|
const canvas = within(canvasElement);
|
|
80
87
|
|
|
81
88
|
const getOption = (name: RegExp | string) => canvas.findByRole("menuitem", { name });
|
|
@@ -150,4 +157,30 @@ GridFormMultiSelectInteractions_.play = async ({ canvasElement }) => {
|
|
|
150
157
|
expect(canvas.queryByText("Zero")).not.toBeInTheDocument();
|
|
151
158
|
expect(canvas.queryByText("Sub component")).not.toBeInTheDocument();
|
|
152
159
|
expect(canvas.queryByText("Other")).not.toBeInTheDocument();
|
|
160
|
+
|
|
161
|
+
userEvent.type(filterText, "x");
|
|
162
|
+
expect(canvas.queryByText("One")).not.toBeInTheDocument();
|
|
163
|
+
expect(canvas.queryByText("No Options")).toBeInTheDocument();
|
|
164
|
+
|
|
165
|
+
// Check enter works to add custom free-text
|
|
166
|
+
userEvent.type(filterText, "{Enter}");
|
|
167
|
+
expect(onSelectFilter).toHaveBeenCalledWith({
|
|
168
|
+
filter: "onx",
|
|
169
|
+
options: [
|
|
170
|
+
{
|
|
171
|
+
...options[0],
|
|
172
|
+
checked: true,
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
...options[1],
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
...options[2],
|
|
179
|
+
checked: true,
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
...options[3],
|
|
183
|
+
},
|
|
184
|
+
],
|
|
185
|
+
});
|
|
153
186
|
};
|
package/src/styles/Grid.scss
CHANGED
|
@@ -9,6 +9,12 @@
|
|
|
9
9
|
flex-direction: column;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
.Grid-wrapper {
|
|
13
|
+
display: flex;
|
|
14
|
+
flex-direction: column;
|
|
15
|
+
flex: 1;
|
|
16
|
+
}
|
|
17
|
+
|
|
12
18
|
.Grid-sortIsStale {
|
|
13
19
|
span.ag-icon.ag-icon-desc::after {
|
|
14
20
|
content: "*";
|
|
@@ -19,12 +25,16 @@
|
|
|
19
25
|
}
|
|
20
26
|
}
|
|
21
27
|
|
|
22
|
-
.Grid-
|
|
28
|
+
.Grid-container-filters {
|
|
23
29
|
width: 100%;
|
|
24
30
|
margin-bottom: 16px;
|
|
25
31
|
flex: 0;
|
|
32
|
+
display: flex;
|
|
33
|
+
flex-direction: row;
|
|
34
|
+
grid-column-gap: 1em;
|
|
26
35
|
}
|
|
27
36
|
|
|
37
|
+
|
|
28
38
|
.Grid-quickFilterBox {
|
|
29
39
|
width: 100%;
|
|
30
40
|
height: 48px;
|