@redsift/ds-mcp-server 12.4.0 → 12.5.0-muiv7

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 (60) hide show
  1. package/data/demos/patterns/_shared/StateDebugPanel.tsx +153 -0
  2. package/data/demos/patterns/_shared/columns.tsx +12 -3
  3. package/data/demos/patterns/_shared/defaults.ts +1 -1
  4. package/data/demos/patterns/_shared/filter-helpers.ts +1 -1
  5. package/data/demos/patterns/_shared/helpers.tsx +1 -1
  6. package/data/demos/patterns/_shared/server-logic.ts +1 -1
  7. package/data/demos/patterns/_shared/story-helpers.ts +578 -14
  8. package/data/demos/patterns/_shared/use-router-adapter.ts +51 -0
  9. package/data/demos/patterns/crossfiltered-datagrid-client-side/CrossfilteredDatagridClientSide.interaction.stories.tsx +2 -2
  10. package/data/demos/patterns/crossfiltered-datagrid-client-side/example.tsx +2 -2
  11. package/data/demos/patterns/crossfiltered-datagrid-server-side/CrossfilteredDatagridServerSide.interaction.stories.tsx +2 -2
  12. package/data/demos/patterns/crossfiltered-datagrid-server-side/example.tsx +6 -6
  13. package/data/demos/patterns/drilldowned-datagrid-client-side/DrilldownedDatagridClientSide.interaction.stories.tsx +2 -2
  14. package/data/demos/patterns/drilldowned-datagrid-client-side/example.tsx +1 -1
  15. package/data/demos/patterns/drilldowned-datagrid-server-side/DrilldownedDatagridServerSide.interaction.stories.tsx +2 -2
  16. package/data/demos/patterns/drilldowned-datagrid-server-side/example.tsx +1 -1
  17. package/data/demos/patterns/single-datagrid-client-side/SingleDatagridClientSide.interaction.stories.tsx +382 -169
  18. package/data/demos/patterns/single-datagrid-client-side/example.tsx +6 -6
  19. package/data/demos/patterns/single-datagrid-client-side/with-empty-state.tsx +1 -1
  20. package/data/demos/patterns/single-datagrid-client-side/with-error.tsx +1 -1
  21. package/data/demos/patterns/single-datagrid-client-side/with-loading.tsx +1 -1
  22. package/data/demos/patterns/single-datagrid-server-side/SingleDatagridServerSide.interaction.stories.tsx +426 -54
  23. package/data/demos/patterns/single-datagrid-server-side/example.tsx +6 -5
  24. package/data/demos/patterns/stateful-single-datagrid-client-side/StatefulSingleDatagridClientSide.interaction.stories.tsx +671 -0
  25. package/data/demos/patterns/stateful-single-datagrid-client-side/example.tsx +55 -0
  26. package/data/demos/patterns/stateful-single-datagrid-client-side/with-empty-state.tsx +27 -0
  27. package/data/demos/patterns/stateful-single-datagrid-client-side/with-error.tsx +39 -0
  28. package/data/demos/patterns/stateful-single-datagrid-client-side/with-loading.tsx +25 -0
  29. package/data/demos/patterns/stateful-single-datagrid-server-side/StatefulSingleDatagridServerSide.interaction.stories.tsx +692 -0
  30. package/data/demos/patterns/stateful-single-datagrid-server-side/example.tsx +108 -0
  31. package/data/demos/patterns/stateful-single-datagrid-server-side/with-empty-state.tsx +31 -0
  32. package/data/demos/patterns/stateful-single-datagrid-server-side/with-error.tsx +43 -0
  33. package/data/demos/patterns/stateful-single-datagrid-server-side/with-loading.tsx +29 -0
  34. package/data/demos/patterns/summary-dashboard/SummaryDashboard.interaction.stories.tsx +44 -24
  35. package/data/demos/patterns/summary-dashboard/example.tsx +66 -28
  36. package/data/demos/patterns/summary-dashboard/with-loading.tsx +12 -3
  37. package/data/demos/patterns/tabbed-datagrid-client-side/TabbedDatagridClientSide.interaction.stories.tsx +2 -2
  38. package/data/demos/patterns/tabbed-datagrid-server-side/TabbedDatagridServerSide.interaction.stories.tsx +2 -2
  39. package/data/demos/patterns/tabbed-datagrid-server-side/example.tsx +1 -1
  40. package/data/docs/components/dashboard/Dashboard.json +2 -2
  41. package/data/docs/components/table/DataGrid.json +30 -3
  42. package/data/docs/components/table/StatefulDataGrid.json +30 -3
  43. package/data/docs/components-index.json +2 -2
  44. package/data/docs/components.json +64 -10
  45. package/data/docs/llms-full.txt +799 -44
  46. package/data/docs/llms.txt +20 -6
  47. package/data/docs/patterns-catalog.md +25 -24
  48. package/data/docs/patterns.json +82 -8
  49. package/data/metadata.json +2 -2
  50. package/data/patterns/crossfiltered-datagrid-server-side.mdx +1 -1
  51. package/data/patterns/drilldowned-datagrid-client-side.mdx +1 -1
  52. package/data/patterns/drilldowned-datagrid-server-side.mdx +1 -1
  53. package/data/patterns/single-datagrid-client-side.mdx +9 -9
  54. package/data/patterns/single-datagrid-server-side.mdx +6 -6
  55. package/data/patterns/stateful-single-datagrid-client-side.mdx +304 -0
  56. package/data/patterns/stateful-single-datagrid-server-side.mdx +347 -0
  57. package/data/patterns/summary-dashboard.mdx +47 -7
  58. package/data/patterns/tabbed-datagrid-client-side.mdx +72 -2
  59. package/data/patterns/tabbed-datagrid-server-side.mdx +105 -2
  60. package/package.json +2 -2
@@ -2,7 +2,7 @@ import React from 'react';
2
2
  import type { Meta, StoryObj } from '@storybook/react';
3
3
  import { within, userEvent } from '@storybook/testing-library';
4
4
  import { expect } from '@storybook/jest';
5
- import { GridFilterModel } from '@mui/x-data-grid-premium';
5
+ import { GridFilterModel } from '@mui/x-data-grid-pro';
6
6
 
7
7
  import Example from './example';
8
8
  import WithLoadingExample from './with-loading';
@@ -16,7 +16,23 @@ import {
16
16
  assertBulkActionBarVisible,
17
17
  assertBulkActionBarHidden,
18
18
  clickRowCheckbox,
19
+ clickHeaderCheckbox,
19
20
  getRetryButton,
21
+ assertDisplayedRowsText,
22
+ clickNextPage,
23
+ clickPrevPage,
24
+ changePageSize,
25
+ openColumnsPanel,
26
+ toggleColumnInPanel,
27
+ closeColumnsPanel,
28
+ assertColumnVisible,
29
+ assertColumnHidden,
30
+ changeDensity,
31
+ assertDensity,
32
+ clickColumnHeader,
33
+ assertColumnSorted,
34
+ assertHeaderCheckboxState,
35
+ assertRowCheckboxChecked,
20
36
  } from '../_shared/story-helpers';
21
37
  import {
22
38
  DEFAULT_FILTER_MODEL_COUNT,
@@ -35,25 +51,106 @@ import {
35
51
  TAGS_HASANYOF_LOCAL_NEW,
36
52
  } from '../_shared/expected-values';
37
53
 
38
- const meta: Meta = {
54
+ const meta: Meta<typeof Example> = {
39
55
  title: 'Patterns/Single Datagrid (Client)',
40
- component: Example as any,
56
+ component: Example,
41
57
  };
42
58
  export default meta;
43
59
  type Story = StoryObj;
44
60
 
45
61
  // ---------------------------------------------------------------------------
46
- // Defaultrenders with DEFAULT_FILTER_MODEL + checkbox select/deselect
62
+ // Helpercreates a filter story
63
+ // ---------------------------------------------------------------------------
64
+
65
+ const makeStory = (filterModel: GridFilterModel, expectedCount: number): Story => ({
66
+ render: () => <Example initialFilterModel={filterModel} />,
67
+ play: async ({ canvasElement, step }) => {
68
+ const canvas = within(canvasElement);
69
+ await step('Wait for grid to load', async () => {
70
+ await waitForGridToLoad(canvas);
71
+ });
72
+ await step(`Assert filtered row count is ${expectedCount}`, async () => {
73
+ await assertRowCount(canvasElement, expectedCount);
74
+ });
75
+ },
76
+ });
77
+
78
+ // Items (string)
79
+ export const FilterItemsContainsBread: Story = makeStory(
80
+ { items: [{ field: 'Items', operator: 'contains', value: 'Bread' }] },
81
+ ITEMS_CONTAINS_BREAD
82
+ );
83
+ export const FilterItemsEqualsCoffee: Story = makeStory(
84
+ { items: [{ field: 'Items', operator: 'equals', value: 'Coffee' }] },
85
+ ITEMS_EQUALS_COFFEE
86
+ );
87
+
88
+ // Category (singleSelect)
89
+ export const FilterCategoryIsBakery: Story = makeStory(
90
+ { items: [{ field: 'Category', operator: 'is', value: 'Bakery' }] },
91
+ CATEGORY_IS_BAKERY
92
+ );
93
+ export const FilterCategoryIsAnyOfBakeryPastry: Story = makeStory(
94
+ { items: [{ field: 'Category', operator: 'isAnyOf', value: ['Bakery', 'Pastry'] }] },
95
+ CATEGORY_ISANYOF_BAKERY_PASTRY
96
+ );
97
+
98
+ // Paid (number)
99
+ export const FilterPaidGreaterThan3: Story = makeStory(
100
+ { items: [{ field: 'Paid', operator: '>', value: 3 }] },
101
+ PAID_GT_3
102
+ );
103
+ export const FilterPaidLessThanOrEqual1_5: Story = makeStory(
104
+ { items: [{ field: 'Paid', operator: '<=', value: 1.5 }] },
105
+ PAID_LTE_1_5
106
+ );
107
+
108
+ // InStock (boolean)
109
+ export const FilterInStockTrue: Story = makeStory(
110
+ { items: [{ field: 'InStock', operator: 'is', value: 'true' }] },
111
+ INSTOCK_TRUE
112
+ );
113
+ export const FilterInStockFalse: Story = makeStory(
114
+ { items: [{ field: 'InStock', operator: 'is', value: 'false' }] },
115
+ INSTOCK_FALSE
116
+ );
117
+
118
+ // Allergens (multiSelect)
119
+ export const FilterAllergensHasAnyOfGluten: Story = makeStory(
120
+ { items: [{ field: 'Allergens', operator: 'hasAnyOf', value: ['Gluten'] }] },
121
+ ALLERGENS_HASANYOF_GLUTEN
122
+ );
123
+ export const FilterAllergensHasAllOfGlutenDairy: Story = makeStory(
124
+ { items: [{ field: 'Allergens', operator: 'hasAllOf', value: ['Gluten', 'Dairy'] }] },
125
+ ALLERGENS_HASALLOF_GLUTEN_DAIRY
126
+ );
127
+
128
+ // Tags (tags)
129
+ export const FilterTagsHasLocal: Story = makeStory(
130
+ { items: [{ field: 'Tags', operator: 'has', value: 'Local' }] },
131
+ TAGS_HAS_LOCAL
132
+ );
133
+ export const FilterTagsHasAnyOfLocalNew: Story = makeStory(
134
+ { items: [{ field: 'Tags', operator: 'hasAnyOf', value: ['Local', 'New'] }] },
135
+ TAGS_HASANYOF_LOCAL_NEW
136
+ );
137
+
138
+ // ---------------------------------------------------------------------------
139
+ // Default — renders with DEFAULT_FILTER_MODEL
47
140
  // ---------------------------------------------------------------------------
48
141
 
49
142
  export const Default: Story = {
50
143
  render: () => <Example />,
51
- play: async ({ canvasElement }) => {
144
+ play: async ({ canvasElement, step }) => {
52
145
  const canvas = within(canvasElement);
53
- await waitForGridToLoad(canvas);
54
146
 
55
- // Default filter model produces 1,284 rows
56
- await assertRowCount(canvasElement, DEFAULT_FILTER_MODEL_COUNT);
147
+ await step('Wait for grid to load', async () => {
148
+ await waitForGridToLoad(canvas);
149
+ });
150
+
151
+ await step('Assert default filter produces correct row count', async () => {
152
+ await assertRowCount(canvasElement, DEFAULT_FILTER_MODEL_COUNT);
153
+ });
57
154
  },
58
155
  };
59
156
 
@@ -63,221 +160,329 @@ export const Default: Story = {
63
160
 
64
161
  export const NoFilter: Story = {
65
162
  render: () => <Example initialFilterModel={{ items: [] }} />,
66
- play: async ({ canvasElement }) => {
163
+ play: async ({ canvasElement, step }) => {
67
164
  const canvas = within(canvasElement);
68
- await waitForGridToLoad(canvas);
69
- await assertRowCount(canvasElement, TOTAL);
165
+
166
+ await step('Wait for grid to load', async () => {
167
+ await waitForGridToLoad(canvas);
168
+ });
169
+
170
+ await step('Assert all rows visible (no filter)', async () => {
171
+ await assertRowCount(canvasElement, TOTAL);
172
+ });
70
173
  },
71
174
  };
72
175
 
73
176
  // ---------------------------------------------------------------------------
74
- // Items (string column) contains / equals
177
+ // Checkbox selectionbulk action bar appears/disappears
75
178
  // ---------------------------------------------------------------------------
76
179
 
77
- const itemsContainsBread: GridFilterModel = {
78
- items: [{ field: 'Items', operator: 'contains', value: 'Bread' }],
79
- };
80
-
81
- export const FilterItemsContainsBread: Story = {
82
- render: () => <Example initialFilterModel={itemsContainsBread} />,
83
- play: async ({ canvasElement }) => {
180
+ export const CheckboxSelectionBulkActions: Story = {
181
+ render: () => <Example initialFilterModel={{ items: [] }} />,
182
+ play: async ({ canvasElement, step }) => {
84
183
  const canvas = within(canvasElement);
85
- await waitForGridToLoad(canvas);
86
- await assertRowCount(canvasElement, ITEMS_CONTAINS_BREAD);
87
- },
88
- };
89
-
90
- const itemsEqualsCoffee: GridFilterModel = {
91
- items: [{ field: 'Items', operator: 'equals', value: 'Coffee' }],
92
- };
93
184
 
94
- export const FilterItemsEqualsCoffee: Story = {
95
- render: () => <Example initialFilterModel={itemsEqualsCoffee} />,
96
- play: async ({ canvasElement }) => {
97
- const canvas = within(canvasElement);
98
- await waitForGridToLoad(canvas);
99
- await assertRowCount(canvasElement, ITEMS_EQUALS_COFFEE);
185
+ await step('Wait for grid to load', async () => {
186
+ await waitForGridToLoad(canvas);
187
+ });
188
+
189
+ await step('Verify no bulk action bar initially', async () => {
190
+ await assertBulkActionBarHidden(canvasElement);
191
+ });
192
+
193
+ await step('Select first row — bulk bar shows 1 selected', async () => {
194
+ await clickRowCheckbox(canvasElement, 0);
195
+ await assertBulkActionBarVisible(canvasElement, 1);
196
+ });
197
+
198
+ await step('Select second row — bulk bar shows 2 selected', async () => {
199
+ await clickRowCheckbox(canvasElement, 1);
200
+ await assertBulkActionBarVisible(canvasElement, 2);
201
+ });
202
+
203
+ await step('Deselect first row — bulk bar shows 1 selected', async () => {
204
+ await clickRowCheckbox(canvasElement, 0);
205
+ await assertBulkActionBarVisible(canvasElement, 1);
206
+ });
207
+
208
+ await step('Deselect last row — bulk bar disappears', async () => {
209
+ await clickRowCheckbox(canvasElement, 1);
210
+ await assertBulkActionBarHidden(canvasElement);
211
+ });
100
212
  },
101
213
  };
102
214
 
103
215
  // ---------------------------------------------------------------------------
104
- // Category (singleSelect column) is / isAnyOf
216
+ // Pagination next/prev and page size
105
217
  // ---------------------------------------------------------------------------
106
218
 
107
- const categoryIsBakery: GridFilterModel = {
108
- items: [{ field: 'Category', operator: 'is', value: 'Bakery' }],
109
- };
110
-
111
- export const FilterCategoryIsBakery: Story = {
112
- render: () => <Example initialFilterModel={categoryIsBakery} />,
113
- play: async ({ canvasElement }) => {
219
+ export const PaginationNextPrev: Story = {
220
+ render: () => <Example initialFilterModel={{ items: [] }} />,
221
+ play: async ({ canvasElement, step }) => {
114
222
  const canvas = within(canvasElement);
115
- await waitForGridToLoad(canvas);
116
- await assertRowCount(canvasElement, CATEGORY_IS_BAKERY);
117
- },
118
- };
119
223
 
120
- const categoryIsAnyOfBakeryPastry: GridFilterModel = {
121
- items: [{ field: 'Category', operator: 'isAnyOf', value: ['Bakery', 'Pastry'] }],
122
- };
224
+ await step('Wait for grid to load', async () => {
225
+ await waitForGridToLoad(canvas);
226
+ });
123
227
 
124
- export const FilterCategoryIsAnyOfBakeryPastry: Story = {
125
- render: () => <Example initialFilterModel={categoryIsAnyOfBakeryPastry} />,
126
- play: async ({ canvasElement }) => {
127
- const canvas = within(canvasElement);
128
- await waitForGridToLoad(canvas);
129
- await assertRowCount(canvasElement, CATEGORY_ISANYOF_BAKERY_PASTRY);
130
- },
131
- };
228
+ await step('Verify initial page shows 1–10 of 14,075', async () => {
229
+ await assertDisplayedRowsText(canvasElement, '1–10 of 14,075');
230
+ });
132
231
 
133
- // ---------------------------------------------------------------------------
134
- // Paid (number column) — > / <=
135
- // ---------------------------------------------------------------------------
232
+ await step('Navigate to next page — shows 11–20', async () => {
233
+ await clickNextPage(canvasElement);
234
+ await assertDisplayedRowsText(canvasElement, '11–20 of 14,075');
235
+ });
136
236
 
137
- const paidGt3: GridFilterModel = {
138
- items: [{ field: 'Paid', operator: '>', value: 3 }],
237
+ await step('Navigate back to first page — shows 1–10', async () => {
238
+ await clickPrevPage(canvasElement);
239
+ await assertDisplayedRowsText(canvasElement, '1–10 of 14,075');
240
+ });
241
+ },
139
242
  };
140
243
 
141
- export const FilterPaidGreaterThan3: Story = {
142
- render: () => <Example initialFilterModel={paidGt3} />,
143
- play: async ({ canvasElement }) => {
244
+ export const PaginationPageSizeChange: Story = {
245
+ render: () => <Example initialFilterModel={{ items: [] }} />,
246
+ play: async ({ canvasElement, step }) => {
144
247
  const canvas = within(canvasElement);
145
- await waitForGridToLoad(canvas);
146
- await assertRowCount(canvasElement, PAID_GT_3);
147
- },
148
- };
149
248
 
150
- const paidLte1_5: GridFilterModel = {
151
- items: [{ field: 'Paid', operator: '<=', value: 1.5 }],
152
- };
249
+ await step('Wait for grid to load', async () => {
250
+ await waitForGridToLoad(canvas);
251
+ });
153
252
 
154
- export const FilterPaidLessThanOrEqual1_5: Story = {
155
- render: () => <Example initialFilterModel={paidLte1_5} />,
156
- play: async ({ canvasElement }) => {
157
- const canvas = within(canvasElement);
158
- await waitForGridToLoad(canvas);
159
- await assertRowCount(canvasElement, PAID_LTE_1_5);
253
+ await step('Verify initial page shows 1–10 of 14,075', async () => {
254
+ await assertDisplayedRowsText(canvasElement, '1–10 of 14,075');
255
+ });
256
+
257
+ await step('Change page size to 25 — shows 1–25', async () => {
258
+ await changePageSize(canvasElement, 25);
259
+ await assertDisplayedRowsText(canvasElement, '1–25 of 14,075');
260
+ });
160
261
  },
161
262
  };
162
263
 
163
264
  // ---------------------------------------------------------------------------
164
- // InStock (boolean column)is true / is false
265
+ // Selection + Paginationselection persists across pages
165
266
  // ---------------------------------------------------------------------------
166
267
 
167
- const inStockTrue: GridFilterModel = {
168
- items: [{ field: 'InStock', operator: 'is', value: 'true' }],
169
- };
170
-
171
- export const FilterInStockTrue: Story = {
172
- render: () => <Example initialFilterModel={inStockTrue} />,
173
- play: async ({ canvasElement }) => {
268
+ export const SelectionPersistsAcrossPages: Story = {
269
+ render: () => <Example initialFilterModel={{ items: [] }} />,
270
+ play: async ({ canvasElement, step }) => {
174
271
  const canvas = within(canvasElement);
175
- await waitForGridToLoad(canvas);
176
- await assertRowCount(canvasElement, INSTOCK_TRUE);
272
+
273
+ await step('Wait for grid to load', async () => {
274
+ await waitForGridToLoad(canvas);
275
+ });
276
+
277
+ await step('Select rows 0 and 1 — verify checkboxes checked', async () => {
278
+ await assertHeaderCheckboxState(canvasElement, 'unchecked');
279
+ await clickRowCheckbox(canvasElement, 0);
280
+ await clickRowCheckbox(canvasElement, 1);
281
+ await assertBulkActionBarVisible(canvasElement, 2);
282
+ await assertRowCheckboxChecked(canvasElement, 0, true);
283
+ await assertRowCheckboxChecked(canvasElement, 1, true);
284
+ await assertHeaderCheckboxState(canvasElement, 'indeterminate');
285
+ });
286
+
287
+ await step('Navigate to next page — selection persists, page actually changed', async () => {
288
+ await clickNextPage(canvasElement);
289
+ await assertDisplayedRowsText(canvasElement, '11–20 of 14,075');
290
+ await assertBulkActionBarVisible(canvasElement, 2);
291
+ await assertHeaderCheckboxState(canvasElement, 'unchecked');
292
+ await assertRowCheckboxChecked(canvasElement, 0, false);
293
+ });
294
+
295
+ await step('Navigate back — rows 0 and 1 still checked', async () => {
296
+ await clickPrevPage(canvasElement);
297
+ await assertDisplayedRowsText(canvasElement, '1–10 of 14,075');
298
+ await assertBulkActionBarVisible(canvasElement, 2);
299
+ await assertRowCheckboxChecked(canvasElement, 0, true);
300
+ await assertRowCheckboxChecked(canvasElement, 1, true);
301
+ await assertHeaderCheckboxState(canvasElement, 'indeterminate');
302
+ });
177
303
  },
178
304
  };
179
305
 
180
- const inStockFalse: GridFilterModel = {
181
- items: [{ field: 'InStock', operator: 'is', value: 'false' }],
306
+ export const SelectAllOnPage: Story = {
307
+ render: () => <Example initialFilterModel={{ items: [] }} />,
308
+ play: async ({ canvasElement, step }) => {
309
+ const canvas = within(canvasElement);
310
+
311
+ await step('Wait for grid to load', async () => {
312
+ await waitForGridToLoad(canvas);
313
+ });
314
+
315
+ await step('Verify header checkbox starts unchecked', async () => {
316
+ await assertHeaderCheckboxState(canvasElement, 'unchecked');
317
+ });
318
+
319
+ await step('Select all on first page — 10 selected, header checked', async () => {
320
+ await clickHeaderCheckbox(canvasElement);
321
+ await assertBulkActionBarVisible(canvasElement, 10);
322
+ await assertHeaderCheckboxState(canvasElement, 'checked');
323
+ await assertDisplayedRowsText(canvasElement, '1–10 of 14,075');
324
+ });
325
+
326
+ await step('Navigate to page 2 — verify page changed, header unchecked', async () => {
327
+ await clickNextPage(canvasElement);
328
+ await assertDisplayedRowsText(canvasElement, '11–20 of 14,075');
329
+ await assertBulkActionBarVisible(canvasElement, 10);
330
+ await assertHeaderCheckboxState(canvasElement, 'unchecked');
331
+ });
332
+
333
+ await step('Select all on page 2 — 20 selected, still on page 2', async () => {
334
+ await clickHeaderCheckbox(canvasElement);
335
+ await assertBulkActionBarVisible(canvasElement, 20);
336
+ await assertHeaderCheckboxState(canvasElement, 'checked');
337
+ await assertDisplayedRowsText(canvasElement, '11–20 of 14,075');
338
+ });
339
+
340
+ await step('Navigate back to page 1 — all rows still checked', async () => {
341
+ await clickPrevPage(canvasElement);
342
+ await assertDisplayedRowsText(canvasElement, '1–10 of 14,075');
343
+ await assertBulkActionBarVisible(canvasElement, 20);
344
+ await assertHeaderCheckboxState(canvasElement, 'checked');
345
+ await assertRowCheckboxChecked(canvasElement, 0, true);
346
+ });
347
+ },
182
348
  };
183
349
 
184
- export const FilterInStockFalse: Story = {
185
- render: () => <Example initialFilterModel={inStockFalse} />,
186
- play: async ({ canvasElement }) => {
350
+ export const PartialDeselectAfterSelectAll: Story = {
351
+ render: () => <Example initialFilterModel={{ items: [] }} />,
352
+ play: async ({ canvasElement, step }) => {
187
353
  const canvas = within(canvasElement);
188
- await waitForGridToLoad(canvas);
189
- await assertRowCount(canvasElement, INSTOCK_FALSE);
354
+
355
+ await step('Wait for grid to load', async () => {
356
+ await waitForGridToLoad(canvas);
357
+ });
358
+
359
+ await step('Select all on page — 10 selected, header checked', async () => {
360
+ await clickHeaderCheckbox(canvasElement);
361
+ await assertBulkActionBarVisible(canvasElement, 10);
362
+ await assertHeaderCheckboxState(canvasElement, 'checked');
363
+ });
364
+
365
+ await step('Deselect row 3 — 9 selected, header indeterminate', async () => {
366
+ await clickRowCheckbox(canvasElement, 3);
367
+ await assertBulkActionBarVisible(canvasElement, 9);
368
+ await assertHeaderCheckboxState(canvasElement, 'indeterminate');
369
+ await assertRowCheckboxChecked(canvasElement, 3, false);
370
+ await assertRowCheckboxChecked(canvasElement, 0, true);
371
+ });
372
+
373
+ await step('Click header to re-select all — 10 selected', async () => {
374
+ await clickHeaderCheckbox(canvasElement);
375
+ await assertBulkActionBarVisible(canvasElement, 10);
376
+ await assertHeaderCheckboxState(canvasElement, 'checked');
377
+ });
378
+
379
+ await step('Click header to deselect all — 0 selected', async () => {
380
+ await clickHeaderCheckbox(canvasElement);
381
+ await assertBulkActionBarHidden(canvasElement);
382
+ await assertHeaderCheckboxState(canvasElement, 'unchecked');
383
+ });
190
384
  },
191
385
  };
192
386
 
193
387
  // ---------------------------------------------------------------------------
194
- // Allergens (multiSelect column) hasAnyOf / hasAllOf
388
+ // Column visibilitytoggle columns on and off
195
389
  // ---------------------------------------------------------------------------
196
390
 
197
- const allergensHasAnyOfGluten: GridFilterModel = {
198
- items: [{ field: 'Allergens', operator: 'hasAnyOf', value: ['Gluten'] }],
199
- };
200
-
201
- export const FilterAllergensHasAnyOfGluten: Story = {
202
- render: () => <Example initialFilterModel={allergensHasAnyOfGluten} />,
203
- play: async ({ canvasElement }) => {
391
+ export const ColumnVisibilityToggle: Story = {
392
+ render: () => <Example initialFilterModel={{ items: [] }} />,
393
+ play: async ({ canvasElement, step }) => {
204
394
  const canvas = within(canvasElement);
205
- await waitForGridToLoad(canvas);
206
- await assertRowCount(canvasElement, ALLERGENS_HASANYOF_GLUTEN);
207
- },
208
- };
209
395
 
210
- const allergensHasAllOfGlutenDairy: GridFilterModel = {
211
- items: [{ field: 'Allergens', operator: 'hasAllOf', value: ['Gluten', 'Dairy'] }],
212
- };
213
-
214
- export const FilterAllergensHasAllOfGlutenDairy: Story = {
215
- render: () => <Example initialFilterModel={allergensHasAllOfGlutenDairy} />,
216
- play: async ({ canvasElement }) => {
217
- const canvas = within(canvasElement);
218
- await waitForGridToLoad(canvas);
219
- await assertRowCount(canvasElement, ALLERGENS_HASALLOF_GLUTEN_DAIRY);
396
+ await step('Wait for grid to load', async () => {
397
+ await waitForGridToLoad(canvas);
398
+ });
399
+
400
+ await step('Verify Date & Time column is visible', async () => {
401
+ await assertColumnVisible(canvasElement, 'Date & Time');
402
+ });
403
+
404
+ await step('Hide Date & Time column', async () => {
405
+ await openColumnsPanel(canvasElement);
406
+ await toggleColumnInPanel('Date & Time');
407
+ await closeColumnsPanel();
408
+ await assertColumnHidden(canvasElement, 'Date & Time');
409
+ });
410
+
411
+ await step('Show Date & Time column again', async () => {
412
+ await openColumnsPanel(canvasElement);
413
+ await toggleColumnInPanel('Date & Time');
414
+ await closeColumnsPanel();
415
+ await assertColumnVisible(canvasElement, 'Date & Time');
416
+ });
220
417
  },
221
418
  };
222
419
 
223
420
  // ---------------------------------------------------------------------------
224
- // Tags (tags column) has / hasAnyOf
421
+ // Density switch between compact, standard, comfortable
225
422
  // ---------------------------------------------------------------------------
226
423
 
227
- const tagsHasLocal: GridFilterModel = {
228
- items: [{ field: 'Tags', operator: 'has', value: 'Local' }],
229
- };
230
-
231
- export const FilterTagsHasLocal: Story = {
232
- render: () => <Example initialFilterModel={tagsHasLocal} />,
233
- play: async ({ canvasElement }) => {
424
+ export const DensitySwitch: Story = {
425
+ render: () => <Example initialFilterModel={{ items: [] }} />,
426
+ play: async ({ canvasElement, step }) => {
234
427
  const canvas = within(canvasElement);
235
- await waitForGridToLoad(canvas);
236
- await assertRowCount(canvasElement, TAGS_HAS_LOCAL);
237
- },
238
- };
239
428
 
240
- const tagsHasAnyOfLocalNew: GridFilterModel = {
241
- items: [{ field: 'Tags', operator: 'hasAnyOf', value: ['Local', 'New'] }],
242
- };
429
+ await step('Wait for grid to load', async () => {
430
+ await waitForGridToLoad(canvas);
431
+ });
243
432
 
244
- export const FilterTagsHasAnyOfLocalNew: Story = {
245
- render: () => <Example initialFilterModel={tagsHasAnyOfLocalNew} />,
246
- play: async ({ canvasElement }) => {
247
- const canvas = within(canvasElement);
248
- await waitForGridToLoad(canvas);
249
- await assertRowCount(canvasElement, TAGS_HASANYOF_LOCAL_NEW);
433
+ await step('Verify default density is Compact', async () => {
434
+ await assertDensity(canvasElement, 'Compact');
435
+ });
436
+
437
+ await step('Switch to Standard density', async () => {
438
+ await changeDensity(canvasElement, 'Standard');
439
+ await assertDensity(canvasElement, 'Standard');
440
+ });
441
+
442
+ await step('Switch to Comfortable density', async () => {
443
+ await changeDensity(canvasElement, 'Comfortable');
444
+ await assertDensity(canvasElement, 'Comfortable');
445
+ });
446
+
447
+ await step('Switch back to Compact density', async () => {
448
+ await changeDensity(canvasElement, 'Compact');
449
+ await assertDensity(canvasElement, 'Compact');
450
+ });
250
451
  },
251
452
  };
252
453
 
253
454
  // ---------------------------------------------------------------------------
254
- // Checkbox selection bulk action bar appears/disappears
455
+ // Sortingclick column header to cycle sort direction
255
456
  // ---------------------------------------------------------------------------
256
457
 
257
- export const CheckboxSelectionBulkActions: Story = {
458
+ export const SortingInteraction: Story = {
258
459
  render: () => <Example initialFilterModel={{ items: [] }} />,
259
- play: async ({ canvasElement }) => {
460
+ play: async ({ canvasElement, step }) => {
260
461
  const canvas = within(canvasElement);
261
- await waitForGridToLoad(canvas);
262
-
263
- // Initially no bulk action bar
264
- await assertBulkActionBarHidden(canvasElement);
265
-
266
- // Select first row via checkbox
267
- await clickRowCheckbox(canvasElement, 0);
268
- await assertBulkActionBarVisible(canvasElement, 1);
269
462
 
270
- // Select second row
271
- await clickRowCheckbox(canvasElement, 1);
272
- await assertBulkActionBarVisible(canvasElement, 2);
273
-
274
- // Deselect first row
275
- await clickRowCheckbox(canvasElement, 0);
276
- await assertBulkActionBarVisible(canvasElement, 1);
277
-
278
- // Deselect second rowbulk action bar disappears
279
- await clickRowCheckbox(canvasElement, 1);
280
- await assertBulkActionBarHidden(canvasElement);
463
+ await step('Wait for grid to load', async () => {
464
+ await waitForGridToLoad(canvas);
465
+ });
466
+
467
+ await step('Verify default sort is DateTime descending', async () => {
468
+ await assertColumnSorted(canvasElement, 'Date & Time', 'descending');
469
+ });
470
+
471
+ await step('Click Item headersort ascending', async () => {
472
+ await clickColumnHeader(canvasElement, 'Item');
473
+ await assertColumnSorted(canvasElement, 'Item', 'ascending');
474
+ await assertColumnSorted(canvasElement, 'Date & Time', 'none');
475
+ });
476
+
477
+ await step('Click Item header again — sort descending', async () => {
478
+ await clickColumnHeader(canvasElement, 'Item');
479
+ await assertColumnSorted(canvasElement, 'Item', 'descending');
480
+ });
481
+
482
+ await step('Click Item header again — unsorted', async () => {
483
+ await clickColumnHeader(canvasElement, 'Item');
484
+ await assertColumnSorted(canvasElement, 'Item', 'none');
485
+ });
281
486
  },
282
487
  };
283
488
 
@@ -287,10 +492,12 @@ export const CheckboxSelectionBulkActions: Story = {
287
492
 
288
493
  export const WithLoading: Story = {
289
494
  render: () => <WithLoadingExample />,
290
- play: async ({ canvasElement }) => {
291
- await assertLoadingVisible(canvasElement);
292
- const grid = canvasElement.querySelector('.MuiDataGrid-root');
293
- expect(grid).toBeTruthy();
495
+ play: async ({ canvasElement, step }) => {
496
+ await step('Assert loading state is visible', async () => {
497
+ await assertLoadingVisible(canvasElement);
498
+ const grid = canvasElement.querySelector('.MuiDataGrid-root');
499
+ expect(grid).toBeTruthy();
500
+ });
294
501
  },
295
502
  };
296
503
 
@@ -308,12 +515,18 @@ export const WithEmptyState: Story = {
308
515
 
309
516
  export const WithError: Story = {
310
517
  render: () => <WithErrorExample />,
311
- play: async ({ canvasElement }) => {
518
+ play: async ({ canvasElement, step }) => {
312
519
  const canvas = within(canvasElement);
313
- await assertErrorBannerVisible(canvas);
314
- const retryButton = getRetryButton(canvas);
315
- expect(retryButton).toBeTruthy();
316
- await userEvent.click(retryButton);
317
- await assertLoadingVisible(canvasElement);
520
+
521
+ await step('Assert error banner is visible', async () => {
522
+ await assertErrorBannerVisible(canvas);
523
+ });
524
+
525
+ await step('Click retry button — loading state appears', async () => {
526
+ const retryButton = getRetryButton(canvas);
527
+ expect(retryButton).toBeTruthy();
528
+ await userEvent.click(retryButton);
529
+ await assertLoadingVisible(canvasElement);
530
+ });
318
531
  },
319
532
  };