@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/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@linzjs/step-ag-grid",
3
3
  "repository": "github:linz/step-ag-grid.git",
4
4
  "license": "MIT",
5
- "version": "29.5.1",
5
+ "version": "29.7.0",
6
6
  "keywords": [
7
7
  "aggrid",
8
8
  "ag-grid",
@@ -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
+ });
@@ -1,4 +1,5 @@
1
1
  export * from './GridFilterButtons';
2
+ export * from './GridFilterColumnsMultiSelect';
2
3
  export * from './GridFilterColumnsToggle';
3
4
  export * from './GridFilterDownloadCsvButton';
4
5
  export * from './GridFilterHeaderIconButton';
@@ -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: 12, desc: 'Backend Developer' },
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;