@linzjs/step-ag-grid 29.5.1 → 29.7.0
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/src/components/gridFilter/GridFilterColumnsMultiSelect.d.ts +30 -0
- package/dist/src/components/gridFilter/index.d.ts +1 -0
- package/dist/step-ag-grid.cjs +136 -27
- package/dist/step-ag-grid.cjs.map +1 -1
- package/dist/step-ag-grid.esm.js +135 -28
- package/dist/step-ag-grid.esm.js.map +1 -1
- package/package.json +1 -1
- package/src/components/gridFilter/GridFilterColumnsMultiSelect.scss +20 -0
- package/src/components/gridFilter/GridFilterColumnsMultiSelect.tsx +189 -0
- package/src/components/gridFilter/index.ts +1 -0
- package/src/stories/grid/GridFilterButtons.stories.tsx +1 -1
- package/src/stories/grid/GridFilterColumnsMultiSelect.stories.tsx +122 -0
package/package.json
CHANGED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
@use "@linzjs/lui/dist/scss/Core" as lui;
|
|
2
|
+
|
|
3
|
+
.GridFilterColsMultiSelect {
|
|
4
|
+
background: #fff;
|
|
5
|
+
padding: 8px;
|
|
6
|
+
@include lui.font-semibold();
|
|
7
|
+
|
|
8
|
+
.LuiCheckboxInput-selectAll,
|
|
9
|
+
.LuiCheckboxInput-item {
|
|
10
|
+
color: lui.$charcoal;
|
|
11
|
+
line-height: 20px;
|
|
12
|
+
margin-bottom: 0;
|
|
13
|
+
font-weight: 600;
|
|
14
|
+
font-style: SemiBold;
|
|
15
|
+
font-size: 16px;
|
|
16
|
+
line-height: 24px;
|
|
17
|
+
letter-spacing: 0%;
|
|
18
|
+
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import './GridFilterColumnsMultiSelect.scss';
|
|
2
|
+
|
|
3
|
+
import { LuiCheckboxInput } from '@linzjs/lui';
|
|
4
|
+
import type { IDoesFilterPassParams, IFilterComp, IFilterParams } from 'ag-grid-community';
|
|
5
|
+
import React from 'react';
|
|
6
|
+
import { createRoot, Root } from 'react-dom/client';
|
|
7
|
+
|
|
8
|
+
export interface CheckboxMultiFilterParams extends IFilterParams {
|
|
9
|
+
labels?: Record<string, string>;
|
|
10
|
+
labelFormatter?: (value: string) => string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface CheckboxMultiFilterModel {
|
|
14
|
+
values: string[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface FilterUIProps {
|
|
18
|
+
allValues: string[];
|
|
19
|
+
selected: Set<string>;
|
|
20
|
+
labels: Record<string, string>;
|
|
21
|
+
labelFormatter?: (value: string) => string;
|
|
22
|
+
onToggleAll: (checked: boolean) => void;
|
|
23
|
+
onToggleOne: (value: string, checked: boolean) => void;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const FilterUI: React.FC<FilterUIProps> = ({
|
|
27
|
+
allValues,
|
|
28
|
+
selected,
|
|
29
|
+
labels,
|
|
30
|
+
labelFormatter,
|
|
31
|
+
onToggleAll,
|
|
32
|
+
onToggleOne,
|
|
33
|
+
}) => {
|
|
34
|
+
const allChecked = allValues.length > 0 && selected.size === allValues.length;
|
|
35
|
+
|
|
36
|
+
const getDisplayLabel = (raw: string): string => {
|
|
37
|
+
const mapped = labels[raw] ?? raw;
|
|
38
|
+
return labelFormatter ? labelFormatter(mapped) : mapped;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<div className="GridFilterColsMultiSelect">
|
|
43
|
+
<span className="LuiSelect-label-text">Filter column</span>
|
|
44
|
+
|
|
45
|
+
<LuiCheckboxInput
|
|
46
|
+
className="LuiCheckboxInput-selectAll"
|
|
47
|
+
label={'Select All'}
|
|
48
|
+
value="true"
|
|
49
|
+
isChecked={allChecked}
|
|
50
|
+
onChange={(e) => onToggleAll(e.target.checked)}
|
|
51
|
+
/>
|
|
52
|
+
{allValues.map((val) => (
|
|
53
|
+
<LuiCheckboxInput
|
|
54
|
+
key={val}
|
|
55
|
+
className="LuiCheckboxInput-item"
|
|
56
|
+
label={getDisplayLabel(val)}
|
|
57
|
+
value={val}
|
|
58
|
+
isChecked={selected.has(val)}
|
|
59
|
+
onChange={(e) => onToggleOne(val, e.target.checked)}
|
|
60
|
+
/>
|
|
61
|
+
))}
|
|
62
|
+
</div>
|
|
63
|
+
);
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export class GridFilterColumnsMultiSelect implements IFilterComp {
|
|
67
|
+
private params!: CheckboxMultiFilterParams;
|
|
68
|
+
private selectedValues = new Set<string>();
|
|
69
|
+
private labels: Record<string, string> = {};
|
|
70
|
+
private allValues: string[] = [];
|
|
71
|
+
private gui!: HTMLElement;
|
|
72
|
+
private labelFormatter?: (value: string) => string;
|
|
73
|
+
private reactRoot: Root | null = null;
|
|
74
|
+
|
|
75
|
+
private loadFieldValues(): string[] {
|
|
76
|
+
const field = this.params.colDef.field as string;
|
|
77
|
+
const values = new Set<string>();
|
|
78
|
+
|
|
79
|
+
this.params.api.forEachNode((node) => {
|
|
80
|
+
const data = node.data;
|
|
81
|
+
const cellValue = data?.[field];
|
|
82
|
+
if (
|
|
83
|
+
data &&
|
|
84
|
+
typeof data === 'object' &&
|
|
85
|
+
field in data &&
|
|
86
|
+
typeof cellValue === 'string' &&
|
|
87
|
+
cellValue !== undefined &&
|
|
88
|
+
cellValue !== null
|
|
89
|
+
) {
|
|
90
|
+
values.add(cellValue);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
return Array.from(values).sort();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
init(params: CheckboxMultiFilterParams): void {
|
|
98
|
+
this.params = params;
|
|
99
|
+
this.labels = { ...params.labels };
|
|
100
|
+
this.labelFormatter = params.labelFormatter;
|
|
101
|
+
|
|
102
|
+
this.allValues = this.loadFieldValues();
|
|
103
|
+
|
|
104
|
+
this.gui = document.createElement('div');
|
|
105
|
+
this.reactRoot = createRoot(this.gui);
|
|
106
|
+
this.render();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
private render(): void {
|
|
110
|
+
if (!this.reactRoot) return;
|
|
111
|
+
|
|
112
|
+
this.reactRoot.render(
|
|
113
|
+
<FilterUI
|
|
114
|
+
allValues={this.allValues}
|
|
115
|
+
selected={this.selectedValues}
|
|
116
|
+
labels={this.labels}
|
|
117
|
+
labelFormatter={this.labelFormatter}
|
|
118
|
+
onToggleAll={this.handleToggleAll.bind(this)}
|
|
119
|
+
onToggleOne={this.handleToggleOne.bind(this)}
|
|
120
|
+
/>,
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
private handleToggleAll(checked: boolean): void {
|
|
125
|
+
if (checked) {
|
|
126
|
+
this.allValues.forEach((val) => this.selectedValues.add(val));
|
|
127
|
+
} else {
|
|
128
|
+
this.selectedValues.clear();
|
|
129
|
+
}
|
|
130
|
+
this.render();
|
|
131
|
+
this.params.filterChangedCallback();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
private handleToggleOne(value: string, checked: boolean): void {
|
|
135
|
+
if (checked) {
|
|
136
|
+
this.selectedValues.add(value);
|
|
137
|
+
} else {
|
|
138
|
+
this.selectedValues.delete(value);
|
|
139
|
+
}
|
|
140
|
+
this.render();
|
|
141
|
+
this.params.filterChangedCallback();
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
getGui(): HTMLElement {
|
|
145
|
+
return this.gui;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
isFilterActive(): boolean {
|
|
149
|
+
return this.selectedValues.size > 0;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
doesFilterPass(params: IDoesFilterPassParams): boolean {
|
|
153
|
+
const field = this.params.colDef.field as string;
|
|
154
|
+
if (!params.data || typeof params.data !== 'object' || !(field in params.data)) {
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const cellValue = (params.data as Record<string, unknown>)[field];
|
|
159
|
+
if (typeof cellValue !== 'string') {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return this.selectedValues.has(cellValue);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
getModel(): CheckboxMultiFilterModel | null {
|
|
167
|
+
return this.selectedValues.size > 0 ? { values: Array.from(this.selectedValues) } : null;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
setModel(model: CheckboxMultiFilterModel | null): void {
|
|
171
|
+
this.selectedValues = new Set(model?.values || []);
|
|
172
|
+
this.render();
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
destroy(): void {
|
|
176
|
+
if (this.reactRoot) {
|
|
177
|
+
this.reactRoot.unmount();
|
|
178
|
+
this.reactRoot = null;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export const createCheckboxMultiFilterParams = (
|
|
184
|
+
labels: Record<string, string> = {},
|
|
185
|
+
labelFormatter?: (value: string) => string,
|
|
186
|
+
): Partial<CheckboxMultiFilterParams> => ({
|
|
187
|
+
labels,
|
|
188
|
+
labelFormatter,
|
|
189
|
+
});
|
|
@@ -73,7 +73,7 @@ const GridFilterButtonsTemplate: StoryFn<typeof Grid<ITestRow>> = (props: GridPr
|
|
|
73
73
|
{ id: 1002, position: 'Manager', age: 65, desc: 'Technical Manager' },
|
|
74
74
|
{ id: 1003, position: 'Tester', age: 30, desc: 'E2E tester' },
|
|
75
75
|
{ id: 1004, position: 'Developer', age: 12, desc: 'Fullstack Developer' },
|
|
76
|
-
{ id: 1005, position: 'Developer', age:
|
|
76
|
+
{ id: 1005, position: 'Developer', age: 14, desc: 'Backend Developer' },
|
|
77
77
|
{ id: 1006, position: 'Architect', age: 30, desc: 'Architect' },
|
|
78
78
|
]);
|
|
79
79
|
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import '../../styles/GridTheme.scss';
|
|
2
|
+
import '../../styles/index.scss';
|
|
3
|
+
import '@linzjs/lui/dist/scss/base.scss';
|
|
4
|
+
import '@linzjs/lui/dist/fonts';
|
|
5
|
+
|
|
6
|
+
import { Meta, StoryFn } from '@storybook/react-vite';
|
|
7
|
+
import {
|
|
8
|
+
createCheckboxMultiFilterParams,
|
|
9
|
+
GridFilterColumnsMultiSelect,
|
|
10
|
+
} from 'components/gridFilter/GridFilterColumnsMultiSelect';
|
|
11
|
+
import { useMemo, useState } from 'react';
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
ColDefT,
|
|
15
|
+
Grid,
|
|
16
|
+
GridCell,
|
|
17
|
+
GridContextProvider,
|
|
18
|
+
GridFilterButtons,
|
|
19
|
+
GridFilterQuick,
|
|
20
|
+
GridFilters,
|
|
21
|
+
GridProps,
|
|
22
|
+
GridUpdatingContextProvider,
|
|
23
|
+
GridWrapper,
|
|
24
|
+
} from '../..';
|
|
25
|
+
import { waitForGridReady } from '../../utils/__tests__/storybookTestUtil';
|
|
26
|
+
|
|
27
|
+
export default {
|
|
28
|
+
title: 'Components / Grids',
|
|
29
|
+
component: Grid,
|
|
30
|
+
decorators: [
|
|
31
|
+
(Story) => (
|
|
32
|
+
<div style={{ width: 1024, height: 400, display: 'flex' }}>
|
|
33
|
+
<GridUpdatingContextProvider>
|
|
34
|
+
<GridContextProvider>
|
|
35
|
+
<Story />
|
|
36
|
+
</GridContextProvider>
|
|
37
|
+
</GridUpdatingContextProvider>
|
|
38
|
+
</div>
|
|
39
|
+
),
|
|
40
|
+
],
|
|
41
|
+
} as Meta<typeof Grid>;
|
|
42
|
+
|
|
43
|
+
interface ITestRow {
|
|
44
|
+
id: number;
|
|
45
|
+
position: string;
|
|
46
|
+
desc: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const GridFilterColumnsMultiSelectTemplate: StoryFn<typeof Grid<ITestRow>> = (props: GridProps<ITestRow>) => {
|
|
50
|
+
const columnDefs: ColDefT<ITestRow>[] = useMemo(
|
|
51
|
+
() => [
|
|
52
|
+
GridCell({
|
|
53
|
+
field: 'id',
|
|
54
|
+
headerName: 'Id',
|
|
55
|
+
}),
|
|
56
|
+
GridCell({
|
|
57
|
+
field: 'position',
|
|
58
|
+
headerName: 'Position',
|
|
59
|
+
filter: GridFilterColumnsMultiSelect,
|
|
60
|
+
filterParams: createCheckboxMultiFilterParams({
|
|
61
|
+
Developer: 'FE Dev',
|
|
62
|
+
Manager: 'Tech Manager',
|
|
63
|
+
}),
|
|
64
|
+
}),
|
|
65
|
+
GridCell({
|
|
66
|
+
field: 'desc',
|
|
67
|
+
headerName: 'Description',
|
|
68
|
+
flex: 1,
|
|
69
|
+
}),
|
|
70
|
+
],
|
|
71
|
+
[],
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
const [rowData] = useState([
|
|
75
|
+
{
|
|
76
|
+
id: 1000,
|
|
77
|
+
position: 'Tester',
|
|
78
|
+
age: 30,
|
|
79
|
+
desc: 'Integration tester - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur a lectus neque. Nunc congue magna ut lorem pretium, vitae congue lorem malesuada. Etiam eget eleifend sapien, sed egestas felis. Aliquam ac augue sapien.',
|
|
80
|
+
},
|
|
81
|
+
{ id: 1001, position: 'Developer', age: 12, desc: 'Frontend developer' },
|
|
82
|
+
{ id: 1002, position: 'Manager', age: 65, desc: 'Technical Manager' },
|
|
83
|
+
{ id: 1003, position: 'Tester', age: 30, desc: 'E2E tester' },
|
|
84
|
+
{ id: 1004, position: 'Developer', age: 12, desc: 'Fullstack Developer' },
|
|
85
|
+
{ id: 1005, position: 'Developer', age: 13, desc: 'Backend Developer' },
|
|
86
|
+
{ id: 1006, position: 'Architect', age: 30, desc: 'Architect' },
|
|
87
|
+
]);
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<GridWrapper>
|
|
91
|
+
<GridFilters>
|
|
92
|
+
<GridFilterQuick quickFilterPlaceholder={'Custom placeholder...'} />
|
|
93
|
+
<GridFilterButtons<ITestRow>
|
|
94
|
+
luiButtonProps={{ style: { whiteSpace: 'nowrap' } }}
|
|
95
|
+
options={[
|
|
96
|
+
{
|
|
97
|
+
label: 'All',
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
label: 'Developers',
|
|
101
|
+
filter: (row) => row.position === 'Developer',
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
label: 'Testers',
|
|
105
|
+
filter: (row) => row.position === 'Tester',
|
|
106
|
+
},
|
|
107
|
+
]}
|
|
108
|
+
/>
|
|
109
|
+
</GridFilters>
|
|
110
|
+
<Grid
|
|
111
|
+
{...props}
|
|
112
|
+
rowSelection={'multiple'}
|
|
113
|
+
columnDefs={columnDefs}
|
|
114
|
+
rowData={rowData}
|
|
115
|
+
sizeColumns={'auto-skip-headers'}
|
|
116
|
+
/>
|
|
117
|
+
</GridWrapper>
|
|
118
|
+
);
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
export const _FilterColumnsMultiSelectExample = GridFilterColumnsMultiSelectTemplate.bind({});
|
|
122
|
+
_FilterColumnsMultiSelectExample.play = waitForGridReady;
|