argent-grid 0.2.0 → 0.3.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.
Files changed (55) hide show
  1. package/AGENTS.md +70 -27
  2. package/e2e/advanced.spec.ts +1 -1
  3. package/e2e/benchmark.spec.ts +7 -7
  4. package/e2e/cell-renderers.spec.ts +152 -0
  5. package/e2e/debug-streaming.spec.ts +31 -0
  6. package/e2e/dnd.spec.ts +73 -0
  7. package/e2e/screenshots.spec.ts +1 -1
  8. package/e2e/visual.spec.ts +30 -9
  9. package/e2e/visual.spec.ts-snapshots/checkbox-renderer-mixed.png +0 -0
  10. package/e2e/visual.spec.ts-snapshots/debug.png +0 -0
  11. package/e2e/visual.spec.ts-snapshots/grid-column-group-headers.png +0 -0
  12. package/e2e/visual.spec.ts-snapshots/grid-default.png +0 -0
  13. package/e2e/visual.spec.ts-snapshots/grid-empty-state.png +0 -0
  14. package/e2e/visual.spec.ts-snapshots/grid-filter-popup.png +0 -0
  15. package/e2e/visual.spec.ts-snapshots/grid-scroll-borders.png +0 -0
  16. package/e2e/visual.spec.ts-snapshots/grid-sidebar-buttons.png +0 -0
  17. package/e2e/visual.spec.ts-snapshots/grid-text-filter.png +0 -0
  18. package/e2e/visual.spec.ts-snapshots/grid-with-selection.png +0 -0
  19. package/e2e/visual.spec.ts-snapshots/rating-renderer-varied.png +0 -0
  20. package/package.json +5 -5
  21. package/plan.md +30 -34
  22. package/src/lib/components/argent-grid.component.css +258 -549
  23. package/src/lib/components/argent-grid.component.html +272 -306
  24. package/src/lib/components/argent-grid.component.ts +585 -135
  25. package/src/lib/components/argent-grid.regressions.spec.ts +301 -0
  26. package/src/lib/components/argent-grid.selection.spec.ts +2 -2
  27. package/src/lib/components/set-filter/set-filter.component.spec.ts +191 -0
  28. package/src/lib/components/set-filter/set-filter.component.ts +7 -2
  29. package/src/lib/rendering/canvas-renderer.spec.ts +148 -1
  30. package/src/lib/rendering/canvas-renderer.ts +177 -286
  31. package/src/lib/rendering/render/cells.ts +122 -5
  32. package/src/lib/rendering/render/column-utils.ts +27 -5
  33. package/src/lib/rendering/render/hit-test.ts +6 -11
  34. package/src/lib/rendering/render/index.ts +15 -6
  35. package/src/lib/rendering/render/lines.ts +12 -6
  36. package/src/lib/rendering/render/primitives.ts +269 -7
  37. package/src/lib/rendering/render/types.ts +2 -1
  38. package/src/lib/rendering/render/walk.ts +39 -19
  39. package/src/lib/services/grid.service.spec.ts +76 -0
  40. package/src/lib/services/grid.service.ts +451 -114
  41. package/src/lib/themes/theme-quartz.ts +2 -2
  42. package/src/lib/types/ag-grid-types.ts +500 -0
  43. package/src/stories/Advanced.stories.ts +78 -17
  44. package/src/stories/ArgentGrid.stories.ts +50 -26
  45. package/src/stories/Benchmark.stories.ts +17 -15
  46. package/src/stories/CellRenderers.stories.ts +205 -31
  47. package/src/stories/Filtering.stories.ts +56 -16
  48. package/src/stories/Grouping.stories.ts +86 -13
  49. package/src/stories/Streaming.stories.ts +57 -0
  50. package/src/stories/Theming.stories.ts +23 -10
  51. package/src/stories/Tooltips.stories.ts +381 -0
  52. package/src/stories/benchmark-wrapper.component.ts +69 -29
  53. package/src/stories/story-utils.ts +88 -0
  54. package/src/stories/streaming-wrapper.component.ts +441 -0
  55. package/tsconfig.json +1 -0
@@ -2,6 +2,7 @@ import { BrowserModule } from '@angular/platform-browser';
2
2
  import type { Meta, StoryObj } from '@storybook/angular';
3
3
  import { moduleMetadata } from '@storybook/angular';
4
4
  import { ArgentGridComponent, ArgentGridModule, themeQuartz } from '../public-api';
5
+ import { STORY_LOCATIONS } from './story-utils';
5
6
 
6
7
  interface Employee {
7
8
  id: number;
@@ -34,7 +35,7 @@ type Story = StoryObj<ArgentGridComponent<Employee>>;
34
35
  function generateStaticData(count: number): Employee[] {
35
36
  const departments = ['Engineering', 'Sales', 'Marketing', 'HR', 'Finance'];
36
37
  const roles = ['Engineer', 'Manager', 'Director', 'VP', 'Intern'];
37
- const locations = ['New York', 'San Francisco', 'London', 'Singapore', 'Remote'];
38
+ const locations = STORY_LOCATIONS;
38
39
  const statuses = ['Active', 'On Leave', 'Remote', 'Travel'];
39
40
 
40
41
  return Array.from({ length: count }, (_, i) => ({
@@ -45,8 +46,9 @@ function generateStaticData(count: number): Employee[] {
45
46
  salary: 50000 + i * 1000,
46
47
  salaryTrend: [10, 20, 30, 40, 50, 60, 70, 80, 90, 100].map((v) => (v + i * 5) % 100),
47
48
  location: locations[i % locations.length],
48
- performance: 60 + (i % 40),
49
- status: statuses[i % statuses.length],
49
+ // Use deterministic "semi-random" patterns based on index for stable E2E screenshots
50
+ performance: 60 + ((i * 7) % 40),
51
+ status: statuses[(i * 3) % statuses.length],
50
52
  }));
51
53
  }
52
54
 
@@ -72,7 +74,7 @@ export const SparklineArea: Story = {
72
74
  },
73
75
  ],
74
76
  rowData: generateStaticData(50),
75
- height: '400px',
77
+ height: 'calc(100vh - 60px)',
76
78
  width: '100%',
77
79
  theme: themeQuartz,
78
80
  },
@@ -106,7 +108,7 @@ export const SparklineLine: Story = {
106
108
  },
107
109
  ],
108
110
  rowData: generateStaticData(50),
109
- height: '400px',
111
+ height: 'calc(100vh - 60px)',
110
112
  width: '100%',
111
113
  theme: themeQuartz,
112
114
  },
@@ -119,7 +121,41 @@ export const SparklineLine: Story = {
119
121
  },
120
122
  };
121
123
 
122
- export const CustomCellRenderer: Story = {
124
+ export const SparklineBar: Story = {
125
+ args: {
126
+ columnDefs: [
127
+ { field: 'id', headerName: 'ID', width: 80 },
128
+ { field: 'name', headerName: 'Name', width: 200 },
129
+ { field: 'department', headerName: 'Department', width: 180 },
130
+ {
131
+ field: 'salaryTrend',
132
+ headerName: 'Salary Trend',
133
+ width: 200,
134
+ cellRenderer: 'sparkline',
135
+ sparklineOptions: {
136
+ type: 'bar',
137
+ bar: {
138
+ fill: '#6366f1',
139
+ strokeWidth: 0,
140
+ },
141
+ },
142
+ },
143
+ ],
144
+ rowData: generateStaticData(50),
145
+ height: 'calc(100vh - 60px)',
146
+ width: '100%',
147
+ theme: themeQuartz,
148
+ },
149
+ parameters: {
150
+ docs: {
151
+ description: {
152
+ story: 'Bar sparkline showing salary trend data as a series of vertical bars.',
153
+ },
154
+ },
155
+ },
156
+ };
157
+
158
+ export const ProgressBar: Story = {
123
159
  args: {
124
160
  columnDefs: [
125
161
  { field: 'id', headerName: 'ID', width: 80 },
@@ -128,28 +164,25 @@ export const CustomCellRenderer: Story = {
128
164
  {
129
165
  field: 'performance',
130
166
  headerName: 'Performance',
131
- width: 150,
132
- cellRenderer: (params: any) => {
133
- const value = params.value;
134
- const color = value >= 80 ? '#22c55e' : value >= 60 ? '#eab308' : '#ef4444';
135
- return `<div style="display: flex; align-items: center; gap: 8px;">
136
- <div style="flex: 1; height: 8px; background: #e5e7eb; border-radius: 4px; overflow: hidden;">
137
- <div style="width: ${value}%; height: 100%; background: ${color}; border-radius: 4px;"></div>
138
- </div>
139
- <span style="color: ${color}; font-weight: 600; min-width: 40px;">${value}%</span>
140
- </div>`;
167
+ width: 180,
168
+ progressOptions: {
169
+ min: 0,
170
+ max: 100,
171
+ fill: (value: number) => (value >= 80 ? '#22c55e' : value >= 60 ? '#eab308' : '#ef4444'),
172
+ showLabel: true,
141
173
  },
142
174
  },
143
175
  ],
144
176
  rowData: generateStaticData(50),
145
- height: '400px',
177
+ height: 'calc(100vh - 60px)',
146
178
  width: '100%',
147
179
  theme: themeQuartz,
148
180
  },
149
181
  parameters: {
150
182
  docs: {
151
183
  description: {
152
- story: 'Custom cell renderer showing performance as a progress bar with color coding.',
184
+ story:
185
+ 'Built-in progress bar cell renderer with traffic-light color coding (green ≥ 80, yellow ≥ 60, red < 60).',
153
186
  },
154
187
  },
155
188
  },
@@ -165,29 +198,86 @@ export const StatusBadge: Story = {
165
198
  field: 'status',
166
199
  headerName: 'Status',
167
200
  width: 120,
168
- cellRenderer: (params: any) => {
169
- const status = params.value;
170
- const colors: Record<string, { bg: string; text: string }> = {
171
- Active: { bg: '#dcfce7', text: '#16a34a' },
172
- 'On Leave': { bg: '#fef3c7', text: '#d97706' },
173
- Remote: { bg: '#dbeafe', text: '#2563eb' },
174
- Travel: { bg: '#f3e8ff', text: '#9333ea' },
175
- };
176
- const { bg, text } = colors[status] || { bg: '#f3f4f6', text: '#6b7280' };
177
- return `<span style="padding: 4px 12px; background: ${bg}; color: ${text}; border-radius: 9999px; font-size: 12px; font-weight: 500;">${status}</span>`;
201
+ badgeOptions: {
202
+ colorMap: {
203
+ Active: { fill: '#dcfce7', text: '#16a34a' },
204
+ 'On Leave': { fill: '#fef3c7', text: '#d97706' },
205
+ Remote: { fill: '#dbeafe', text: '#2563eb' },
206
+ Travel: { fill: '#f3e8ff', text: '#9333ea' },
207
+ },
208
+ defaultColors: { fill: '#f3f4f6', text: '#6b7280' },
178
209
  },
179
210
  },
180
211
  { field: 'salary', headerName: 'Salary', width: 120 },
181
212
  ],
182
213
  rowData: generateStaticData(50),
183
- height: '400px',
214
+ height: 'calc(100vh - 60px)',
184
215
  width: '100%',
185
216
  theme: themeQuartz,
186
217
  },
187
218
  parameters: {
188
219
  docs: {
189
220
  description: {
190
- story: 'Custom status badge cell renderer with different colors for each status type.',
221
+ story:
222
+ 'Built-in badge cell renderer with per-value color mapping (green = Active, yellow = On Leave, blue = Remote, purple = Travel).',
223
+ },
224
+ },
225
+ },
226
+ };
227
+
228
+ export const ButtonCell: Story = {
229
+ args: {
230
+ columnDefs: [
231
+ { field: 'id', headerName: 'ID', width: 80 },
232
+ { field: 'name', headerName: 'Name', width: 200 },
233
+ { field: 'department', headerName: 'Department', width: 180 },
234
+ {
235
+ field: 'status',
236
+ headerName: 'Action',
237
+ width: 140,
238
+ buttonOptions: {
239
+ label: 'View Details',
240
+ variant: 'primary',
241
+ onClick: (params: any) => {
242
+ alert(`Clicked row ${params.node.rowIndex}: ${params.data.name}`);
243
+ },
244
+ },
245
+ },
246
+ {
247
+ field: 'status',
248
+ headerName: 'Delete',
249
+ width: 120,
250
+ buttonOptions: {
251
+ label: 'Remove',
252
+ variant: 'danger',
253
+ onClick: (params: any) => {
254
+ alert(`Delete ${params.data.name}?`);
255
+ },
256
+ },
257
+ },
258
+ {
259
+ field: 'status',
260
+ headerName: 'Export',
261
+ width: 120,
262
+ buttonOptions: {
263
+ label: 'Export',
264
+ variant: 'secondary',
265
+ onClick: (params: any) => {
266
+ alert(`Export ${params.data.name}`);
267
+ },
268
+ },
269
+ },
270
+ ],
271
+ rowData: generateStaticData(50),
272
+ height: 'calc(100vh - 60px)',
273
+ width: '100%',
274
+ theme: themeQuartz,
275
+ },
276
+ parameters: {
277
+ docs: {
278
+ description: {
279
+ story:
280
+ 'Built-in button cell renderer with `primary`, `danger`, and `secondary` variants. `onClick` receives AG Grid-compatible params: `{ value, data, node, api, colDef, event }`.',
191
281
  },
192
282
  },
193
283
  },
@@ -207,7 +297,7 @@ export const CurrencyFormatter: Story = {
207
297
  },
208
298
  ],
209
299
  rowData: generateStaticData(50),
210
- height: '400px',
300
+ height: 'calc(100vh - 60px)',
211
301
  width: '100%',
212
302
  theme: themeQuartz,
213
303
  },
@@ -219,3 +309,87 @@ export const CurrencyFormatter: Story = {
219
309
  },
220
310
  },
221
311
  };
312
+
313
+ export const CheckboxRenderer: Story = {
314
+ args: {
315
+ columnDefs: [
316
+ { field: 'id', headerName: 'ID', width: 80 },
317
+ { field: 'name', headerName: 'Name', width: 200 },
318
+ {
319
+ field: 'performance',
320
+ headerName: 'High Perf?',
321
+ width: 120,
322
+ cellRenderer: 'checkbox',
323
+ // Example: boolean logic for checkbox
324
+ valueGetter: (params: any) => params.data.performance >= 80,
325
+ },
326
+ {
327
+ field: 'status',
328
+ headerName: 'Remote?',
329
+ width: 120,
330
+ cellRenderer: 'checkbox',
331
+ valueGetter: (params: any) => params.data.status === 'Remote',
332
+ },
333
+ ],
334
+ rowData: generateStaticData(50),
335
+ height: 'calc(100vh - 60px)',
336
+ width: '100%',
337
+ theme: themeQuartz,
338
+ },
339
+ parameters: {
340
+ docs: {
341
+ description: {
342
+ story:
343
+ 'Dedicated **checkbox cell renderer**. Renders a boolean value as a centered checkbox. ' +
344
+ 'Can be used with `valueGetter` to derive boolean state from complex data.',
345
+ },
346
+ },
347
+ },
348
+ };
349
+
350
+ export const RatingRenderer: Story = {
351
+ args: {
352
+ columnDefs: [
353
+ { field: 'id', headerName: 'ID', width: 80 },
354
+ { field: 'name', headerName: 'Name', width: 200 },
355
+ {
356
+ field: 'performance',
357
+ headerName: 'Performance',
358
+ width: 150,
359
+ cellRenderer: 'rating',
360
+ // Scale 60-100 to 0-5 stars
361
+ valueGetter: (params: any) => (params.data.performance - 60) / 8,
362
+ ratingOptions: {
363
+ color: '#ffb400',
364
+ size: 16,
365
+ },
366
+ },
367
+ {
368
+ field: 'performance',
369
+ headerName: 'Stars (Small)',
370
+ width: 120,
371
+ cellRenderer: 'rating',
372
+ valueGetter: (params: any) => (params.data.performance - 60) / 8,
373
+ ratingOptions: {
374
+ color: '#3b82f6',
375
+ size: 10,
376
+ max: 5,
377
+ },
378
+ },
379
+ ],
380
+ rowData: generateStaticData(50),
381
+ height: 'calc(100vh - 60px)',
382
+ width: '100%',
383
+ theme: themeQuartz,
384
+ },
385
+ parameters: {
386
+ docs: {
387
+ description: {
388
+ story:
389
+ '**Rating cell renderer** using SVG-style star shapes on Canvas. ' +
390
+ 'Supports custom colors, sizes, and max star count. ' +
391
+ 'Great for visualising scores or feedback.',
392
+ },
393
+ },
394
+ },
395
+ };
@@ -2,6 +2,14 @@ import { BrowserModule } from '@angular/platform-browser';
2
2
  import type { Meta, StoryObj } from '@storybook/angular';
3
3
  import { moduleMetadata } from '@storybook/angular';
4
4
  import { ArgentGridComponent, ArgentGridModule, themeQuartz } from '../public-api';
5
+ import {
6
+ departmentValueFormatter,
7
+ locationValueFormatter,
8
+ roleValueFormatter,
9
+ STORY_DEPARTMENTS,
10
+ STORY_LOCATIONS,
11
+ STORY_ROLES,
12
+ } from './story-utils';
5
13
 
6
14
  interface Employee {
7
15
  id: number;
@@ -30,9 +38,9 @@ export default meta;
30
38
  type Story = StoryObj<ArgentGridComponent<Employee>>;
31
39
 
32
40
  function generateStaticData(count: number): Employee[] {
33
- const departments = ['Engineering', 'Sales', 'Marketing'];
34
- const roles = ['Engineer', 'Manager', 'Analyst'];
35
- const locations = ['New York', 'London', 'Berlin'];
41
+ const departments = STORY_DEPARTMENTS;
42
+ const roles = STORY_ROLES;
43
+ const locations = STORY_LOCATIONS;
36
44
 
37
45
  return Array.from({ length: count }, (_, i) => ({
38
46
  id: i + 1,
@@ -64,6 +72,7 @@ export const TextFilter: Story = {
64
72
  filter: 'text',
65
73
  floatingFilter: true,
66
74
  headerComponentParams: { filterIcon: '🔤' },
75
+ valueFormatter: departmentValueFormatter,
67
76
  },
68
77
  {
69
78
  field: 'role',
@@ -72,12 +81,18 @@ export const TextFilter: Story = {
72
81
  filter: 'text',
73
82
  floatingFilter: true,
74
83
  headerComponentParams: { filterIcon: '🔤' },
84
+ valueFormatter: roleValueFormatter,
75
85
  },
76
86
  { field: 'salary', headerName: 'Salary', width: 120 },
77
- { field: 'location', headerName: 'Location', width: 150 },
87
+ {
88
+ field: 'location',
89
+ headerName: 'Location',
90
+ width: 150,
91
+ valueFormatter: locationValueFormatter,
92
+ },
78
93
  ],
79
94
  rowData: generateStaticData(50),
80
- height: '500px',
95
+ height: 'calc(100vh - 60px)',
81
96
  width: '100%',
82
97
  theme: themeQuartz,
83
98
  },
@@ -96,14 +111,19 @@ export const NumberFilter: Story = {
96
111
  columnDefs: [
97
112
  {
98
113
  field: 'id',
99
- headerName: 'ID 🔢',
114
+ headerName: 'ID',
100
115
  width: 80,
101
116
  filter: 'number',
102
117
  floatingFilter: true,
103
118
  headerComponentParams: { filterIcon: '🔢' },
104
119
  },
105
120
  { field: 'name', headerName: 'Name', width: 200 },
106
- { field: 'department', headerName: 'Department', width: 180 },
121
+ {
122
+ field: 'department',
123
+ headerName: 'Department',
124
+ width: 180,
125
+ valueFormatter: departmentValueFormatter,
126
+ },
107
127
  {
108
128
  field: 'salary',
109
129
  headerName: 'Salary 🔢',
@@ -112,10 +132,15 @@ export const NumberFilter: Story = {
112
132
  floatingFilter: true,
113
133
  headerComponentParams: { filterIcon: '🔢' },
114
134
  },
115
- { field: 'location', headerName: 'Location', width: 150 },
135
+ {
136
+ field: 'location',
137
+ headerName: 'Location',
138
+ width: 150,
139
+ valueFormatter: locationValueFormatter,
140
+ },
116
141
  ],
117
142
  rowData: generateStaticData(50),
118
- height: '500px',
143
+ height: 'calc(100vh - 60px)',
119
144
  width: '100%',
120
145
  theme: themeQuartz,
121
146
  },
@@ -141,8 +166,9 @@ export const SetFilter: Story = {
141
166
  filter: 'set',
142
167
  floatingFilter: true,
143
168
  headerComponentParams: { filterIcon: '☑️' },
169
+ valueFormatter: departmentValueFormatter,
144
170
  },
145
- { field: 'role', headerName: 'Role', width: 250 },
171
+ { field: 'role', headerName: 'Role', width: 250, valueFormatter: roleValueFormatter },
146
172
  { field: 'salary', headerName: 'Salary', width: 120 },
147
173
  {
148
174
  field: 'location',
@@ -151,10 +177,11 @@ export const SetFilter: Story = {
151
177
  filter: 'set',
152
178
  floatingFilter: true,
153
179
  headerComponentParams: { filterIcon: '☑️' },
180
+ valueFormatter: locationValueFormatter,
154
181
  },
155
182
  ],
156
183
  rowData: generateStaticData(50),
157
- height: '500px',
184
+ height: 'calc(100vh - 60px)',
158
185
  width: '100%',
159
186
  theme: themeQuartz,
160
187
  },
@@ -173,12 +200,24 @@ export const HiddenFloatingFilters: Story = {
173
200
  columnDefs: [
174
201
  { field: 'id', headerName: 'ID', width: 80 },
175
202
  { field: 'name', headerName: 'Name', width: 200, filter: 'text' },
176
- { field: 'department', headerName: 'Department', width: 180, filter: 'text' },
177
- { field: 'role', headerName: 'Role', width: 250, filter: 'text' },
203
+ {
204
+ field: 'department',
205
+ headerName: 'Department',
206
+ width: 180,
207
+ filter: 'text',
208
+ valueFormatter: departmentValueFormatter,
209
+ },
210
+ {
211
+ field: 'role',
212
+ headerName: 'Role',
213
+ width: 250,
214
+ filter: 'text',
215
+ valueFormatter: roleValueFormatter,
216
+ },
178
217
  { field: 'salary', headerName: 'Salary', width: 120, filter: 'number' },
179
218
  ],
180
219
  rowData: generateStaticData(50),
181
- height: '500px',
220
+ height: 'calc(100vh - 60px)',
182
221
  width: '100%',
183
222
  theme: themeQuartz,
184
223
  },
@@ -197,7 +236,7 @@ export const AllFilterTypes: Story = {
197
236
  columnDefs: [
198
237
  {
199
238
  field: 'id',
200
- headerName: 'ID 🔢',
239
+ headerName: 'ID',
201
240
  width: 80,
202
241
  filter: 'number',
203
242
  floatingFilter: true,
@@ -218,6 +257,7 @@ export const AllFilterTypes: Story = {
218
257
  filter: 'set',
219
258
  floatingFilter: true,
220
259
  headerComponentParams: { filterIcon: '☑️' },
260
+ valueFormatter: departmentValueFormatter,
221
261
  },
222
262
  {
223
263
  field: 'startDate',
@@ -237,7 +277,7 @@ export const AllFilterTypes: Story = {
237
277
  },
238
278
  ],
239
279
  rowData: generateStaticData(50),
240
- height: '500px',
280
+ height: 'calc(100vh - 60px)',
241
281
  width: '100%',
242
282
  theme: themeQuartz,
243
283
  },
@@ -2,6 +2,14 @@ import { BrowserModule } from '@angular/platform-browser';
2
2
  import type { Meta, StoryObj } from '@storybook/angular';
3
3
  import { moduleMetadata } from '@storybook/angular';
4
4
  import { ArgentGridComponent, ArgentGridModule, themeQuartz } from '../public-api';
5
+ import {
6
+ departmentValueFormatter,
7
+ locationValueFormatter,
8
+ roleValueFormatter,
9
+ STORY_DEPARTMENTS,
10
+ STORY_LOCATIONS,
11
+ STORY_ROLES,
12
+ } from './story-utils';
5
13
 
6
14
  interface Employee {
7
15
  id: number;
@@ -29,9 +37,9 @@ export default meta;
29
37
  type Story = StoryObj<ArgentGridComponent<Employee>>;
30
38
 
31
39
  function generateStaticData(count: number): Employee[] {
32
- const departments = ['Engineering', 'Sales', 'Marketing', 'HR', 'Finance'];
33
- const roles = ['Engineer', 'Manager', 'Director', 'VP', 'Intern'];
34
- const locations = ['New York', 'San Francisco', 'London', 'Singapore', 'Remote'];
40
+ const departments = STORY_DEPARTMENTS;
41
+ const roles = STORY_ROLES;
42
+ const locations = STORY_LOCATIONS;
35
43
 
36
44
  return Array.from({ length: count }, (_, i) => ({
37
45
  id: i + 1,
@@ -54,13 +62,19 @@ export const RowGrouping: Story = {
54
62
  width: 180,
55
63
  rowGroup: true,
56
64
  headerComponentParams: { groupIcon: '📁' },
65
+ valueFormatter: departmentValueFormatter,
57
66
  },
58
- { field: 'role', headerName: 'Role', width: 250 },
67
+ { field: 'role', headerName: 'Role', width: 250, valueFormatter: roleValueFormatter },
59
68
  { field: 'salary', headerName: 'Salary', width: 120 },
60
- { field: 'location', headerName: 'Location', width: 150 },
69
+ {
70
+ field: 'location',
71
+ headerName: 'Location',
72
+ width: 150,
73
+ valueFormatter: locationValueFormatter,
74
+ },
61
75
  ],
62
76
  rowData: generateStaticData(100),
63
- height: '500px',
77
+ height: 'calc(100vh - 60px)',
64
78
  width: '100%',
65
79
  theme: themeQuartz,
66
80
  gridOptions: {
@@ -93,6 +107,7 @@ export const MultiLevelGrouping: Story = {
93
107
  width: 150,
94
108
  rowGroup: true,
95
109
  headerComponentParams: { groupIcon: '📁' },
110
+ valueFormatter: departmentValueFormatter,
96
111
  },
97
112
  {
98
113
  field: 'location',
@@ -100,12 +115,13 @@ export const MultiLevelGrouping: Story = {
100
115
  width: 150,
101
116
  rowGroup: true,
102
117
  headerComponentParams: { groupIcon: '📁' },
118
+ valueFormatter: locationValueFormatter,
103
119
  },
104
- { field: 'role', headerName: 'Role', width: 250 },
120
+ { field: 'role', headerName: 'Role', width: 250, valueFormatter: roleValueFormatter },
105
121
  { field: 'salary', headerName: 'Salary', width: 120 },
106
122
  ],
107
123
  rowData: generateStaticData(100),
108
- height: '500px',
124
+ height: 'calc(100vh - 60px)',
109
125
  width: '100%',
110
126
  theme: themeQuartz,
111
127
  gridOptions: {
@@ -138,18 +154,24 @@ export const GroupingWithAggregation: Story = {
138
154
  width: 180,
139
155
  rowGroup: true,
140
156
  headerComponentParams: { groupIcon: '📁' },
157
+ valueFormatter: departmentValueFormatter,
141
158
  },
142
- { field: 'role', headerName: 'Role', width: 250 },
159
+ { field: 'role', headerName: 'Role', width: 250, valueFormatter: roleValueFormatter },
143
160
  {
144
161
  field: 'salary',
145
162
  headerName: 'Salary 💰',
146
163
  width: 120,
147
164
  aggFunc: 'sum', // Show sum in group rows
148
165
  },
149
- { field: 'location', headerName: 'Location', width: 150 },
166
+ {
167
+ field: 'location',
168
+ headerName: 'Location',
169
+ width: 150,
170
+ valueFormatter: locationValueFormatter,
171
+ },
150
172
  ],
151
173
  rowData: generateStaticData(100),
152
- height: '500px',
174
+ height: 'calc(100vh - 60px)',
153
175
  width: '100%',
154
176
  theme: themeQuartz,
155
177
  gridOptions: {
@@ -182,6 +204,7 @@ export const DragAndDropGrouping: Story = {
182
204
  width: 180,
183
205
  rowGroup: false, // Not pre-grouped - user can drag
184
206
  headerComponentParams: { draggable: true },
207
+ valueFormatter: departmentValueFormatter,
185
208
  },
186
209
  {
187
210
  field: 'location',
@@ -189,12 +212,13 @@ export const DragAndDropGrouping: Story = {
189
212
  width: 150,
190
213
  rowGroup: false,
191
214
  headerComponentParams: { draggable: true },
215
+ valueFormatter: locationValueFormatter,
192
216
  },
193
- { field: 'role', headerName: 'Role', width: 250 },
217
+ { field: 'role', headerName: 'Role', width: 250, valueFormatter: roleValueFormatter },
194
218
  { field: 'salary', headerName: 'Salary', width: 120 },
195
219
  ],
196
220
  rowData: generateStaticData(100),
197
- height: '500px',
221
+ height: 'calc(100vh - 60px)',
198
222
  width: '100%',
199
223
  theme: themeQuartz,
200
224
  gridOptions: {
@@ -215,3 +239,52 @@ export const DragAndDropGrouping: Story = {
215
239
  },
216
240
  },
217
241
  };
242
+
243
+ export const ColumnGroups: Story = {
244
+ args: {
245
+ columnDefs: [
246
+ {
247
+ headerName: 'Group A',
248
+ children: [
249
+ { field: 'id', headerName: 'ID', width: 80 },
250
+ { field: 'name', headerName: 'Name', width: 200 },
251
+ ],
252
+ },
253
+ {
254
+ headerName: 'Group B',
255
+ children: [
256
+ {
257
+ field: 'department',
258
+ headerName: 'Department',
259
+ width: 180,
260
+ valueFormatter: departmentValueFormatter,
261
+ },
262
+ {
263
+ headerName: 'Deep Group',
264
+ children: [
265
+ { field: 'role', headerName: 'Role', width: 250, valueFormatter: roleValueFormatter },
266
+ { field: 'salary', headerName: 'Salary', width: 120 },
267
+ ],
268
+ },
269
+ ],
270
+ },
271
+ {
272
+ field: 'location',
273
+ headerName: 'Location',
274
+ width: 150,
275
+ valueFormatter: locationValueFormatter,
276
+ },
277
+ ],
278
+ rowData: generateStaticData(50),
279
+ height: 'calc(100vh - 60px)',
280
+ width: '100%',
281
+ theme: themeQuartz,
282
+ },
283
+ parameters: {
284
+ docs: {
285
+ description: {
286
+ story: '**Nested column groups**. Demonstrates multiple levels of header grouping.',
287
+ },
288
+ },
289
+ },
290
+ };