@redsift/ds-mcp-server 12.5.2-muiv7 → 12.5.2
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/data/demos/patterns/_shared/StateDebugPanel.tsx +2 -2
- package/data/demos/patterns/_shared/columns.tsx +3 -3
- package/data/demos/patterns/_shared/defaults.ts +1 -1
- package/data/demos/patterns/_shared/filter-helpers.ts +1 -1
- package/data/demos/patterns/_shared/helpers.tsx +1 -1
- package/data/demos/patterns/_shared/server-logic.ts +1 -1
- package/data/demos/patterns/_shared/story-helpers.ts +106 -36
- package/data/demos/patterns/crossfiltered-datagrid-client-side/CrossfilteredDatagridClientSide.interaction.stories.tsx +4 -19
- package/data/demos/patterns/crossfiltered-datagrid-client-side/example.tsx +2 -2
- package/data/demos/patterns/crossfiltered-datagrid-server-side/CrossfilteredDatagridServerSide.interaction.stories.tsx +3 -3
- package/data/demos/patterns/crossfiltered-datagrid-server-side/example.tsx +5 -5
- package/data/demos/patterns/drilldowned-datagrid-client-side/DrilldownedDatagridClientSide.interaction.stories.tsx +2 -2
- package/data/demos/patterns/drilldowned-datagrid-client-side/example.tsx +1 -1
- package/data/demos/patterns/drilldowned-datagrid-server-side/DrilldownedDatagridServerSide.interaction.stories.tsx +2 -2
- package/data/demos/patterns/drilldowned-datagrid-server-side/example.tsx +5 -19
- package/data/demos/patterns/single-datagrid-client-side/SingleDatagridClientSide.interaction.stories.tsx +3 -3
- package/data/demos/patterns/single-datagrid-client-side/example.tsx +5 -5
- package/data/demos/patterns/single-datagrid-server-side/SingleDatagridServerSide.interaction.stories.tsx +3 -3
- package/data/demos/patterns/single-datagrid-server-side/example.tsx +5 -6
- package/data/demos/patterns/stateful-single-datagrid-client-side/StatefulSingleDatagridClientSide.interaction.stories.tsx +130 -3
- package/data/demos/patterns/stateful-single-datagrid-client-side/example.tsx +6 -6
- package/data/demos/patterns/stateful-single-datagrid-server-side/StatefulSingleDatagridServerSide.interaction.stories.tsx +136 -6
- package/data/demos/patterns/stateful-single-datagrid-server-side/example.tsx +6 -9
- package/data/demos/patterns/summary-dashboard/SummaryDashboard.interaction.stories.tsx +2 -2
- package/data/demos/patterns/tabbed-datagrid-client-side/TabbedDatagridClientSide.interaction.stories.tsx +2 -2
- package/data/demos/patterns/tabbed-datagrid-server-side/TabbedDatagridServerSide.interaction.stories.tsx +2 -2
- package/data/demos/patterns/tabbed-datagrid-server-side/example.tsx +1 -1
- package/data/docs/components/dashboard/Dashboard.json +2 -2
- package/data/docs/components/table/DataGrid.json +3 -30
- package/data/docs/components/table/StatefulDataGrid.json +3 -30
- package/data/docs/components-index.json +2 -2
- package/data/docs/components.json +10 -58
- package/data/docs/llms-full.txt +40 -46
- package/data/docs/llms.txt +6 -6
- package/data/docs/patterns-catalog.md +24 -25
- package/data/docs/patterns.json +4 -4
- package/data/metadata.json +2 -2
- package/data/patterns/crossfiltered-datagrid-server-side.mdx +1 -1
- package/data/patterns/drilldowned-datagrid-client-side.mdx +1 -1
- package/data/patterns/drilldowned-datagrid-server-side.mdx +1 -1
- package/data/patterns/single-datagrid-client-side.mdx +9 -9
- package/data/patterns/single-datagrid-server-side.mdx +4 -4
- package/data/patterns/stateful-single-datagrid-client-side.mdx +36 -20
- package/data/patterns/stateful-single-datagrid-server-side.mdx +46 -18
- package/data/patterns/tabbed-datagrid-server-side.mdx +1 -1
- package/package.json +2 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useCallback, useEffect, useState } from 'react';
|
|
2
|
-
import type {
|
|
2
|
+
import type { GridApiPremium } from '@mui/x-data-grid-premium';
|
|
3
3
|
|
|
4
4
|
// localStorage key categories — must match @redsift/table internals
|
|
5
5
|
const LS_CATEGORIES = [
|
|
@@ -13,7 +13,7 @@ const LS_CATEGORIES = [
|
|
|
13
13
|
];
|
|
14
14
|
|
|
15
15
|
interface StateDebugPanelProps {
|
|
16
|
-
apiRef: React.MutableRefObject<
|
|
16
|
+
apiRef: React.MutableRefObject<GridApiPremium | null>;
|
|
17
17
|
useRouter: () => { pathname: string; search: string; historyReplace: (newSearch: string) => void };
|
|
18
18
|
localStorageVersion?: number;
|
|
19
19
|
}
|
|
@@ -2,7 +2,7 @@ import React from 'react';
|
|
|
2
2
|
import { createColumn, TextCell } from '@redsift/table';
|
|
3
3
|
import { Flexbox, Icon, IconButtonLink, Pill } from '@redsift/design-system';
|
|
4
4
|
import { mdiArrowRight, mdiCheck, mdiClose } from '@redsift/icons';
|
|
5
|
-
import { GridColDef, GridRenderCellParams } from '@mui/x-data-grid-
|
|
5
|
+
import { GridColDef, GridRenderCellParams } from '@mui/x-data-grid-premium';
|
|
6
6
|
import { Row } from './data';
|
|
7
7
|
|
|
8
8
|
// -- Option constants -------------------------------------------------------
|
|
@@ -110,7 +110,7 @@ export const columns: GridColDef<Row>[] = [
|
|
|
110
110
|
width: 140,
|
|
111
111
|
display: 'flex',
|
|
112
112
|
...createColumn('date'),
|
|
113
|
-
valueGetter: (value:
|
|
113
|
+
valueGetter: (value: string) => parseDate(value),
|
|
114
114
|
renderCell: ({ value }: GridRenderCellParams) => <TextCell>{value ? formatDate(value as Date) : '—'}</TextCell>,
|
|
115
115
|
},
|
|
116
116
|
// DateTime
|
|
@@ -120,7 +120,7 @@ export const columns: GridColDef<Row>[] = [
|
|
|
120
120
|
width: 180,
|
|
121
121
|
display: 'flex',
|
|
122
122
|
...createColumn('dateTime'),
|
|
123
|
-
valueGetter: (value:
|
|
123
|
+
valueGetter: (value: string) => parseDate(value),
|
|
124
124
|
renderCell: ({ value }: GridRenderCellParams) => <TextCell>{value ? formatDateTime(value as Date) : '—'}</TextCell>,
|
|
125
125
|
},
|
|
126
126
|
// Boolean — in stock
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { GridFilterModel, GridSortModel } from '@mui/x-data-grid-
|
|
1
|
+
import { GridFilterModel, GridSortModel } from '@mui/x-data-grid-premium';
|
|
2
2
|
import { Row, allRows } from './data';
|
|
3
3
|
import { Aggregates, computeAggregates, applyFilters } from './filter-helpers';
|
|
4
4
|
|
|
@@ -366,22 +366,22 @@ export const clickHeaderCheckbox = async (canvasElement: HTMLElement) => {
|
|
|
366
366
|
|
|
367
367
|
/** Click a row checkbox by row index (0-based, within the visible page). */
|
|
368
368
|
export const clickRowCheckbox = async (canvasElement: HTMLElement, rowIndex: number) => {
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
369
|
+
// Wait for BOTH the row AND its checkbox to be present.
|
|
370
|
+
// Server-side grids may render rows before checkboxes are mounted.
|
|
371
|
+
await waitFor(
|
|
372
|
+
() => {
|
|
373
|
+
const rows = canvasElement.querySelectorAll('.MuiDataGrid-row');
|
|
374
|
+
expect(rows.length).toBeGreaterThan(rowIndex);
|
|
375
|
+
const cb = rows[rowIndex].querySelector('input[type="checkbox"]');
|
|
376
|
+
expect(cb).toBeTruthy();
|
|
377
|
+
},
|
|
378
|
+
{ timeout: 5000 }
|
|
379
|
+
);
|
|
380
|
+
const rows = canvasElement.querySelectorAll('.MuiDataGrid-row');
|
|
381
|
+
const checkbox = rows[rowIndex].querySelector('input[type="checkbox"]')!;
|
|
377
382
|
await userEvent.click(checkbox);
|
|
378
|
-
// Wait for
|
|
379
|
-
await
|
|
380
|
-
const freshRow = canvasElement.querySelector(`.MuiDataGrid-row[data-rowindex="${rowIndex}"]`);
|
|
381
|
-
const freshCheckbox = freshRow?.querySelector('input[type="checkbox"]') as HTMLInputElement | null;
|
|
382
|
-
expect(freshCheckbox).toBeTruthy();
|
|
383
|
-
expect(freshCheckbox!.checked).toBe(!wasChecked);
|
|
384
|
-
});
|
|
383
|
+
// Wait for React and MUI DataGrid to process the selection change
|
|
384
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
385
385
|
};
|
|
386
386
|
|
|
387
387
|
// ---------------------------------------------------------------------------
|
|
@@ -952,29 +952,42 @@ export const waitForPaginationEnabled = async (canvasElement: HTMLElement, direc
|
|
|
952
952
|
|
|
953
953
|
/**
|
|
954
954
|
* Change the page size via the MUI pagination "Rows per page" select.
|
|
955
|
-
*
|
|
955
|
+
* MUI Material v7's `useSlot()` applies `.MuiTablePagination-select` to the
|
|
956
|
+
* Select wrapper, NOT the inner display div that has the `onMouseDown` handler.
|
|
957
|
+
* We target `[role="combobox"]` inside the pagination toolbar instead, which is
|
|
958
|
+
* the actual interactive element rendered by `SelectInput`.
|
|
959
|
+
* `userEvent.click` in the Storybook Playwright runner doesn't reliably
|
|
960
|
+
* trigger mouseDown on MUI Select, so we use `fireEvent.mouseDown` directly,
|
|
961
|
+
* then `userEvent.click` to select the option in the dropdown.
|
|
956
962
|
*/
|
|
957
963
|
export const changePageSize = async (canvasElement: HTMLElement, newSize: number) => {
|
|
958
|
-
// Wait for the
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
964
|
+
// Wait for the pagination combobox to be present
|
|
965
|
+
await waitFor(
|
|
966
|
+
() => {
|
|
967
|
+
const el = canvasElement.querySelector('.MuiTablePagination-toolbar [role="combobox"]');
|
|
968
|
+
expect(el).toBeTruthy();
|
|
969
|
+
},
|
|
970
|
+
{ timeout: 5000 }
|
|
971
|
+
);
|
|
972
|
+
|
|
973
|
+
const select = canvasElement.querySelector('.MuiTablePagination-toolbar [role="combobox"]') as HTMLElement;
|
|
974
|
+
|
|
975
|
+
// Use fireEvent.mouseDown to reliably open MUI v7 Select dropdown
|
|
968
976
|
fireEvent.mouseDown(select);
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
977
|
+
|
|
978
|
+
// Wait for the dropdown to render (MUI uses a portal on document.body)
|
|
979
|
+
await waitFor(
|
|
980
|
+
() => {
|
|
981
|
+
const options = document.querySelectorAll('[role="option"]');
|
|
982
|
+
expect(options.length).toBeGreaterThan(0);
|
|
983
|
+
},
|
|
984
|
+
{ timeout: 5000 }
|
|
985
|
+
);
|
|
986
|
+
|
|
987
|
+
const options = document.querySelectorAll('[role="option"]');
|
|
975
988
|
const target = Array.from(options).find((opt) => opt.textContent === String(newSize));
|
|
976
989
|
if (!target) throw new Error(`Could not find page size option "${newSize}"`);
|
|
977
|
-
|
|
990
|
+
await userEvent.click(target);
|
|
978
991
|
};
|
|
979
992
|
|
|
980
993
|
// ---------------------------------------------------------------------------
|
|
@@ -988,7 +1001,7 @@ export const openColumnsPanel = async (canvasElement: HTMLElement) => {
|
|
|
988
1001
|
) as HTMLElement | null;
|
|
989
1002
|
if (!btn) throw new Error('Could not find columns toolbar button');
|
|
990
1003
|
await userEvent.click(btn);
|
|
991
|
-
// Wait for the panel to appear on document.body
|
|
1004
|
+
// Wait for the panel to appear on document.body
|
|
992
1005
|
await waitFor(() => {
|
|
993
1006
|
const panel = document.querySelector('.MuiDataGrid-columnsManagement');
|
|
994
1007
|
expect(panel).toBeTruthy();
|
|
@@ -999,8 +1012,9 @@ export const openColumnsPanel = async (canvasElement: HTMLElement) => {
|
|
|
999
1012
|
export const toggleColumnInPanel = async (fieldLabel: string) => {
|
|
1000
1013
|
const panel = document.querySelector('.MuiDataGrid-columnsManagement');
|
|
1001
1014
|
if (!panel) throw new Error('Columns panel is not open');
|
|
1002
|
-
|
|
1003
|
-
const
|
|
1015
|
+
// MUI v8 renders each column toggle as a baseCheckbox slot with class columnsManagementRow
|
|
1016
|
+
const rows = Array.from(panel.querySelectorAll('.MuiDataGrid-columnsManagementRow'));
|
|
1017
|
+
const target = rows.find((row) => row.textContent?.includes(fieldLabel));
|
|
1004
1018
|
if (!target) throw new Error(`Could not find column "${fieldLabel}" in columns panel`);
|
|
1005
1019
|
const checkbox = target.querySelector('input[type="checkbox"]') as HTMLElement | null;
|
|
1006
1020
|
if (!checkbox) throw new Error(`Could not find checkbox for column "${fieldLabel}"`);
|
|
@@ -1204,6 +1218,12 @@ interface SyncAssertionOptions {
|
|
|
1204
1218
|
checkDensity?: boolean;
|
|
1205
1219
|
/** Whether to check column order sync */
|
|
1206
1220
|
checkColumnOrder?: boolean;
|
|
1221
|
+
/** Whether to check row grouping sync */
|
|
1222
|
+
checkRowGrouping?: boolean;
|
|
1223
|
+
/** Whether to check aggregation sync */
|
|
1224
|
+
checkAggregation?: boolean;
|
|
1225
|
+
/** Whether to check pivot sync */
|
|
1226
|
+
checkPivot?: boolean;
|
|
1207
1227
|
}
|
|
1208
1228
|
|
|
1209
1229
|
/**
|
|
@@ -1222,6 +1242,9 @@ export const assertAllStatesInSync = async ({
|
|
|
1222
1242
|
checkPagination = true,
|
|
1223
1243
|
checkDensity = true,
|
|
1224
1244
|
checkColumnOrder = false,
|
|
1245
|
+
checkRowGrouping = false,
|
|
1246
|
+
checkAggregation = false,
|
|
1247
|
+
checkPivot = false,
|
|
1225
1248
|
}: SyncAssertionOptions) => {
|
|
1226
1249
|
await waitFor(
|
|
1227
1250
|
() => {
|
|
@@ -1328,6 +1351,53 @@ export const assertAllStatesInSync = async ({
|
|
|
1328
1351
|
expect(urlColumnOrder).toBe(inner);
|
|
1329
1352
|
}
|
|
1330
1353
|
}
|
|
1354
|
+
|
|
1355
|
+
// --- Row Grouping ---
|
|
1356
|
+
if (checkRowGrouping) {
|
|
1357
|
+
const lsKey = `${pathname}:${localStorageVersion}:rowGroupingModel`;
|
|
1358
|
+
const lsRaw = localStorage.getItem(lsKey);
|
|
1359
|
+
const lsRowGrouping = lsRaw ? JSON.parse(lsRaw) : '';
|
|
1360
|
+
|
|
1361
|
+
const urlRowGrouping = url.get('_rowGrouping');
|
|
1362
|
+
if (lsRowGrouping && urlRowGrouping) {
|
|
1363
|
+
// localStorage: `_rowGrouping=[a,b,c]`
|
|
1364
|
+
// URL: `_rowGrouping=a,b,c`
|
|
1365
|
+
const lsParams = new URLSearchParams(lsRowGrouping);
|
|
1366
|
+
const lsValue = lsParams.get('_rowGrouping') ?? '';
|
|
1367
|
+
const inner = lsValue.startsWith('[') && lsValue.endsWith(']') ? lsValue.slice(1, -1) : lsValue;
|
|
1368
|
+
expect(urlRowGrouping).toBe(inner);
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
// --- Aggregation ---
|
|
1373
|
+
if (checkAggregation) {
|
|
1374
|
+
const lsKey = `${pathname}:${localStorageVersion}:aggregationModel`;
|
|
1375
|
+
const lsRaw = localStorage.getItem(lsKey);
|
|
1376
|
+
const lsAggregation = lsRaw ? JSON.parse(lsRaw) : '';
|
|
1377
|
+
|
|
1378
|
+
const urlAggregation = url.get('_aggregation');
|
|
1379
|
+
if (lsAggregation && urlAggregation) {
|
|
1380
|
+
// Both use same format: `_aggregation=field.func,...`
|
|
1381
|
+
const lsParams = new URLSearchParams(lsAggregation);
|
|
1382
|
+
const lsValue = lsParams.get('_aggregation') ?? '';
|
|
1383
|
+
expect(urlAggregation).toBe(lsValue);
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
// --- Pivot ---
|
|
1388
|
+
if (checkPivot) {
|
|
1389
|
+
const lsKey = `${pathname}:${localStorageVersion}:pivotModel`;
|
|
1390
|
+
const lsRaw = localStorage.getItem(lsKey);
|
|
1391
|
+
const lsPivot = lsRaw ? JSON.parse(lsRaw) : '';
|
|
1392
|
+
|
|
1393
|
+
const urlPivot = url.get('_pivot');
|
|
1394
|
+
if (lsPivot && urlPivot) {
|
|
1395
|
+
// Both use same format: `_pivot=cols:f1;rows:f2;vals:f3.sum`
|
|
1396
|
+
const lsParams = new URLSearchParams(lsPivot);
|
|
1397
|
+
const lsValue = lsParams.get('_pivot') ?? '';
|
|
1398
|
+
expect(urlPivot).toBe(lsValue);
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1331
1401
|
},
|
|
1332
1402
|
{ timeout: 5000 }
|
|
1333
1403
|
);
|
|
@@ -92,9 +92,9 @@ import {
|
|
|
92
92
|
RU7_RM_PASTRY,
|
|
93
93
|
} from '../_shared/expected-values';
|
|
94
94
|
|
|
95
|
-
const meta: Meta
|
|
95
|
+
const meta: Meta = {
|
|
96
96
|
title: 'Patterns/Crossfiltered Datagrid (Client)',
|
|
97
|
-
component: Example,
|
|
97
|
+
component: Example as any,
|
|
98
98
|
};
|
|
99
99
|
export default meta;
|
|
100
100
|
type Story = StoryObj;
|
|
@@ -127,8 +127,7 @@ const assertState = async (
|
|
|
127
127
|
};
|
|
128
128
|
|
|
129
129
|
// ---------------------------------------------------------------------------
|
|
130
|
-
// Comprehensive toggle sequence
|
|
131
|
-
// each story stays within the Storybook test-runner's timeout.
|
|
130
|
+
// Comprehensive toggle sequence.
|
|
132
131
|
//
|
|
133
132
|
// Forward: 11 clicks across all 5 dimensions (Category, InStock, Allergens,
|
|
134
133
|
// Items, Tags). Then uncheck all 8 persistent filters one by one.
|
|
@@ -139,7 +138,7 @@ const assertState = async (
|
|
|
139
138
|
// filtered aggregates exclude the queried field's own filter.
|
|
140
139
|
// ---------------------------------------------------------------------------
|
|
141
140
|
|
|
142
|
-
export const
|
|
141
|
+
export const ComprehensiveToggleSequence: Story = {
|
|
143
142
|
render: () => <Example />,
|
|
144
143
|
play: async ({ canvasElement }) => {
|
|
145
144
|
const canvas = within(canvasElement);
|
|
@@ -231,20 +230,6 @@ export const ComprehensiveToggleForward: Story = {
|
|
|
231
230
|
// -- U8: -Seasonal (bar) → back to full dataset -------------------------
|
|
232
231
|
await clickBarChartBar(canvasElement, 'Seasonal');
|
|
233
232
|
await assertState(canvasElement, S0_NONE, categoryListbox, allergenListbox);
|
|
234
|
-
},
|
|
235
|
-
};
|
|
236
|
-
|
|
237
|
-
export const ComprehensiveToggleReverse: Story = {
|
|
238
|
-
render: () => <Example />,
|
|
239
|
-
play: async ({ canvasElement }) => {
|
|
240
|
-
const canvas = within(canvasElement);
|
|
241
|
-
await waitForGridToLoad(canvas);
|
|
242
|
-
|
|
243
|
-
const categoryListbox = getListbox(canvas, /filter by category/i);
|
|
244
|
-
const allergenListbox = getListbox(canvas, /filter by allergen/i);
|
|
245
|
-
|
|
246
|
-
// == Step 0: No filters — full dataset ==================================
|
|
247
|
-
await assertState(canvasElement, S0_NONE, categoryListbox, allergenListbox);
|
|
248
233
|
|
|
249
234
|
// == Reverse sequence (add filters in opposite order) ===================
|
|
250
235
|
|
|
@@ -18,7 +18,7 @@ export default () => (
|
|
|
18
18
|
{/* Category card — two-way: counts update when other cards filter */}
|
|
19
19
|
<WithFilters
|
|
20
20
|
field="Category"
|
|
21
|
-
dimension={(d:
|
|
21
|
+
dimension={(d: any) => d.Category}
|
|
22
22
|
datagridCategoryDimFilter={{ field: 'Category', operator: 'isAnyOf' }}
|
|
23
23
|
syncMode="two-way"
|
|
24
24
|
>
|
|
@@ -51,7 +51,7 @@ export default () => (
|
|
|
51
51
|
{/* Allergens card — two-way */}
|
|
52
52
|
<WithFilters
|
|
53
53
|
field="Allergens"
|
|
54
|
-
dimension={(d:
|
|
54
|
+
dimension={(d: any) => d.Allergens}
|
|
55
55
|
isDimensionArray
|
|
56
56
|
datagridCategoryDimFilter={{ field: 'Allergens', operator: 'hasAnyOf' }}
|
|
57
57
|
syncMode="two-way"
|
|
@@ -92,9 +92,9 @@ import {
|
|
|
92
92
|
RU7_RM_PASTRY,
|
|
93
93
|
} from '../_shared/expected-values';
|
|
94
94
|
|
|
95
|
-
const meta: Meta
|
|
95
|
+
const meta: Meta = {
|
|
96
96
|
title: 'Patterns/Crossfiltered Datagrid (Server)',
|
|
97
|
-
component: Example,
|
|
97
|
+
component: Example as any,
|
|
98
98
|
parameters: {
|
|
99
99
|
msw: { handlers: bakeryHandlers },
|
|
100
100
|
},
|
|
@@ -133,7 +133,7 @@ const assertState = async (
|
|
|
133
133
|
// Comprehensive toggle sequence — identical to client but with server waits.
|
|
134
134
|
//
|
|
135
135
|
// Split into Forward and Reverse halves so each story stays within the
|
|
136
|
-
// Storybook test-runner's timeout (38 total steps is too many
|
|
136
|
+
// Storybook test-runner's 15-second timeout (38 total steps is too many).
|
|
137
137
|
// ---------------------------------------------------------------------------
|
|
138
138
|
|
|
139
139
|
export const ComprehensiveToggleForward: Story = {
|
|
@@ -4,7 +4,7 @@ import { Flexbox } from '@redsift/design-system';
|
|
|
4
4
|
import { DataCard, DataRow } from '@redsift/dashboard';
|
|
5
5
|
import { BarChart, PieChart, ArcDatum, BarDatum } from '@redsift/charts';
|
|
6
6
|
import { mdiShapeOutline, mdiToggleSwitch, mdiFoodOff } from '@redsift/icons';
|
|
7
|
-
import { GridFilterModel, GridSortModel } from '@mui/x-data-grid-
|
|
7
|
+
import { GridFilterModel, GridSortModel } from '@mui/x-data-grid-premium';
|
|
8
8
|
import { Row } from '../_shared/data';
|
|
9
9
|
import { columns, CATEGORY_OPTIONS, ALLERGEN_OPTIONS } from '../_shared/columns';
|
|
10
10
|
import { fetchBakeryData, FetchResult } from '../_shared/api-client';
|
|
@@ -212,7 +212,7 @@ export default () => {
|
|
|
212
212
|
data={itemsChartData}
|
|
213
213
|
sliceRole="option"
|
|
214
214
|
onSliceClick={(datum: ArcDatum) => {
|
|
215
|
-
const key = datum.data.key
|
|
215
|
+
const key = datum.data.key;
|
|
216
216
|
setFilterModel((prev) => {
|
|
217
217
|
const selected = getSelectedFromFilterModel(prev, 'Items');
|
|
218
218
|
const next = selected.includes(key) ? selected.filter((v) => v !== key) : [...selected, key];
|
|
@@ -221,7 +221,7 @@ export default () => {
|
|
|
221
221
|
setPage(0);
|
|
222
222
|
}}
|
|
223
223
|
isSliceSelected={(datum: ArcDatum) =>
|
|
224
|
-
itemsSelection.length === 0 || itemsSelection.includes(datum.data.key
|
|
224
|
+
itemsSelection.length === 0 || itemsSelection.includes(datum.data.key)
|
|
225
225
|
}
|
|
226
226
|
/>
|
|
227
227
|
|
|
@@ -247,7 +247,7 @@ export default () => {
|
|
|
247
247
|
data={tagsChartData}
|
|
248
248
|
barRole="option"
|
|
249
249
|
onBarClick={(datum: BarDatum) => {
|
|
250
|
-
const key = datum.data.key
|
|
250
|
+
const key = String(datum.data.key);
|
|
251
251
|
setFilterModel((prev) => {
|
|
252
252
|
const selected = getSelectedFromFilterModel(prev, 'Tags');
|
|
253
253
|
const next = selected.includes(key) ? selected.filter((v) => v !== key) : [...selected, key];
|
|
@@ -256,7 +256,7 @@ export default () => {
|
|
|
256
256
|
setPage(0);
|
|
257
257
|
}}
|
|
258
258
|
isBarSelected={(datum: BarDatum) =>
|
|
259
|
-
tagsSelection.length === 0 || tagsSelection.includes(datum.data.key
|
|
259
|
+
tagsSelection.length === 0 || tagsSelection.includes(String(datum.data.key))
|
|
260
260
|
}
|
|
261
261
|
/>
|
|
262
262
|
</Flexbox>
|
|
@@ -35,9 +35,9 @@ import {
|
|
|
35
35
|
ALLERGENS_HASANYOF_GLUTEN,
|
|
36
36
|
} from '../_shared/expected-values';
|
|
37
37
|
|
|
38
|
-
const meta: Meta
|
|
38
|
+
const meta: Meta = {
|
|
39
39
|
title: 'Patterns/Drilldowned Datagrid (Client)',
|
|
40
|
-
component: Example,
|
|
40
|
+
component: Example as any,
|
|
41
41
|
};
|
|
42
42
|
export default meta;
|
|
43
43
|
type Story = StoryObj;
|
|
@@ -3,7 +3,7 @@ import { DataGrid } from '@redsift/table';
|
|
|
3
3
|
import { Flexbox } from '@redsift/design-system';
|
|
4
4
|
import { DataCard, DataRow } from '@redsift/dashboard';
|
|
5
5
|
import { mdiShapeOutline, mdiToggleSwitch, mdiFoodOff } from '@redsift/icons';
|
|
6
|
-
import { GridFilterModel } from '@mui/x-data-grid-
|
|
6
|
+
import { GridFilterModel } from '@mui/x-data-grid-premium';
|
|
7
7
|
import { rows } from '../_shared/data';
|
|
8
8
|
import { columns, CATEGORY_OPTIONS, ALLERGEN_OPTIONS } from '../_shared/columns';
|
|
9
9
|
import { CustomToolbar } from '../_shared/helpers';
|
|
@@ -37,9 +37,9 @@ import {
|
|
|
37
37
|
ALLERGENS_HASANYOF_GLUTEN,
|
|
38
38
|
} from '../_shared/expected-values';
|
|
39
39
|
|
|
40
|
-
const meta: Meta
|
|
40
|
+
const meta: Meta = {
|
|
41
41
|
title: 'Patterns/Drilldowned Datagrid (Server)',
|
|
42
|
-
component: Example,
|
|
42
|
+
component: Example as any,
|
|
43
43
|
parameters: {
|
|
44
44
|
msw: { handlers: bakeryHandlers },
|
|
45
45
|
},
|
|
@@ -3,7 +3,7 @@ import { DataGrid } from '@redsift/table';
|
|
|
3
3
|
import { Flexbox } from '@redsift/design-system';
|
|
4
4
|
import { DataCard, DataRow } from '@redsift/dashboard';
|
|
5
5
|
import { mdiShapeOutline, mdiToggleSwitch, mdiFoodOff } from '@redsift/icons';
|
|
6
|
-
import { GridFilterModel, GridSortModel } from '@mui/x-data-grid-
|
|
6
|
+
import { GridFilterModel, GridSortModel } from '@mui/x-data-grid-premium';
|
|
7
7
|
import { Row } from '../_shared/data';
|
|
8
8
|
import { columns, CATEGORY_OPTIONS, ALLERGEN_OPTIONS } from '../_shared/columns';
|
|
9
9
|
import { fetchBakeryData, FetchResult } from '../_shared/api-client';
|
|
@@ -101,29 +101,15 @@ export default () => {
|
|
|
101
101
|
});
|
|
102
102
|
}, []);
|
|
103
103
|
|
|
104
|
-
// Track the latest filterModel in a ref so handleFilterModelChange can compare
|
|
105
|
-
// incoming models without needing filterModel in its dependency array.
|
|
106
|
-
const filterModelRef = useRef(filterModel);
|
|
107
|
-
filterModelRef.current = filterModel;
|
|
108
|
-
|
|
109
104
|
const handleFilterModelChange = useCallback((model: GridFilterModel) => {
|
|
110
105
|
if (isInternalUpdate.current) {
|
|
111
106
|
isInternalUpdate.current = false;
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
107
|
+
setFilterModel((prev) => {
|
|
108
|
+
if (JSON.stringify(prev.items) === JSON.stringify(model.items)) return prev;
|
|
109
|
+
return model;
|
|
110
|
+
});
|
|
115
111
|
return;
|
|
116
112
|
}
|
|
117
|
-
// MUI v7 may fire additional onFilterModelChange echoes (with new IDs) after
|
|
118
|
-
// a DataCard update. If the incoming model is semantically identical to current
|
|
119
|
-
// state (same fields, operators, values — ignoring IDs), skip it.
|
|
120
|
-
const normalize = (items: GridFilterModel['items']) =>
|
|
121
|
-
items
|
|
122
|
-
.map(({ field, operator, value }) => `${field}|${operator}|${JSON.stringify(value)}`)
|
|
123
|
-
.sort()
|
|
124
|
-
.join(';;');
|
|
125
|
-
if (normalize(model.items) === normalize(filterModelRef.current.items)) return;
|
|
126
|
-
|
|
127
113
|
// Panel change — debounce and update cardControlledFields.
|
|
128
114
|
if (debounceRef.current) clearTimeout(debounceRef.current);
|
|
129
115
|
debounceRef.current = setTimeout(() => {
|
|
@@ -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-
|
|
5
|
+
import { GridFilterModel } from '@mui/x-data-grid-premium';
|
|
6
6
|
|
|
7
7
|
import Example from './example';
|
|
8
8
|
import WithLoadingExample from './with-loading';
|
|
@@ -52,9 +52,9 @@ import {
|
|
|
52
52
|
TAGS_HASANYOF_LOCAL_NEW,
|
|
53
53
|
} from '../_shared/expected-values';
|
|
54
54
|
|
|
55
|
-
const meta: Meta
|
|
55
|
+
const meta: Meta = {
|
|
56
56
|
title: 'Patterns/Single Datagrid (Client)',
|
|
57
|
-
component: Example,
|
|
57
|
+
component: Example as any,
|
|
58
58
|
};
|
|
59
59
|
export default meta;
|
|
60
60
|
type Story = StoryObj;
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
import React, { useState } from 'react';
|
|
2
|
-
import { DataGrid } from '@redsift/table';
|
|
2
|
+
import { DataGrid, EMPTY_ROW_SELECTION_MODEL, getSelectionCount } from '@redsift/table';
|
|
3
3
|
import { Flexbox } from '@redsift/design-system';
|
|
4
|
-
import { GridFilterModel, GridRowSelectionModel } from '@mui/x-data-grid-
|
|
4
|
+
import { GridFilterModel, GridRowSelectionModel } from '@mui/x-data-grid-premium';
|
|
5
5
|
import { rows } from '../_shared/data';
|
|
6
6
|
import { columns } from '../_shared/columns';
|
|
7
7
|
import { CustomToolbar, BulkActionBar } from '../_shared/helpers';
|
|
8
8
|
import { DEFAULT_FILTER_MODEL, DEFAULT_SORT_MODEL } from '../_shared/defaults';
|
|
9
9
|
|
|
10
10
|
export default ({ initialFilterModel }: { initialFilterModel?: GridFilterModel }) => {
|
|
11
|
-
const [selectionModel, setSelectionModel] = useState<GridRowSelectionModel>(
|
|
11
|
+
const [selectionModel, setSelectionModel] = useState<GridRowSelectionModel>(EMPTY_ROW_SELECTION_MODEL);
|
|
12
12
|
|
|
13
13
|
return (
|
|
14
14
|
<div style={{ width: '100%' }}>
|
|
15
15
|
<Flexbox flexDirection="column" gap="0px">
|
|
16
16
|
<BulkActionBar
|
|
17
|
-
count={selectionModel
|
|
17
|
+
count={getSelectionCount(selectionModel)}
|
|
18
18
|
onLog={() => console.log('Selected:', selectionModel)}
|
|
19
19
|
onDelete={() => console.log('Delete:', selectionModel)}
|
|
20
20
|
/>
|
|
@@ -22,8 +22,8 @@ export default ({ initialFilterModel }: { initialFilterModel?: GridFilterModel }
|
|
|
22
22
|
<DataGrid
|
|
23
23
|
autoHeight
|
|
24
24
|
pagination
|
|
25
|
+
density="compact"
|
|
25
26
|
initialState={{
|
|
26
|
-
density: 'compact',
|
|
27
27
|
filter: { filterModel: initialFilterModel ?? DEFAULT_FILTER_MODEL },
|
|
28
28
|
sorting: { sortModel: DEFAULT_SORT_MODEL },
|
|
29
29
|
pagination: { paginationModel: { page: 0, pageSize: 10 } },
|
|
@@ -2,7 +2,7 @@ import React from 'react';
|
|
|
2
2
|
import type { Meta, StoryObj } from '@storybook/react';
|
|
3
3
|
import { within, userEvent, waitFor } from '@storybook/testing-library';
|
|
4
4
|
import { expect } from '@storybook/jest';
|
|
5
|
-
import { GridFilterModel } from '@mui/x-data-grid-
|
|
5
|
+
import { GridFilterModel } from '@mui/x-data-grid-premium';
|
|
6
6
|
|
|
7
7
|
import Example from './example';
|
|
8
8
|
import WithLoadingExample from './with-loading';
|
|
@@ -55,9 +55,9 @@ import {
|
|
|
55
55
|
TAGS_HASANYOF_LOCAL_NEW,
|
|
56
56
|
} from '../_shared/expected-values';
|
|
57
57
|
|
|
58
|
-
const meta: Meta
|
|
58
|
+
const meta: Meta = {
|
|
59
59
|
title: 'Patterns/Single Datagrid (Server)',
|
|
60
|
-
component: Example,
|
|
60
|
+
component: Example as any,
|
|
61
61
|
parameters: {
|
|
62
62
|
msw: { handlers: bakeryHandlers },
|
|
63
63
|
},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
|
-
import { DataGrid } from '@redsift/table';
|
|
2
|
+
import { DataGrid, EMPTY_ROW_SELECTION_MODEL, getSelectionCount } from '@redsift/table';
|
|
3
3
|
import { Flexbox } from '@redsift/design-system';
|
|
4
|
-
import { GridFilterModel, GridRowSelectionModel, GridSortModel } from '@mui/x-data-grid-
|
|
4
|
+
import { GridFilterModel, GridRowSelectionModel, GridSortModel } from '@mui/x-data-grid-premium';
|
|
5
5
|
import { Row } from '../_shared/data';
|
|
6
6
|
import { columns } from '../_shared/columns';
|
|
7
7
|
import { fetchBakeryData } from '../_shared/api-client';
|
|
@@ -16,7 +16,7 @@ export default ({ initialFilterModel }: { initialFilterModel?: GridFilterModel }
|
|
|
16
16
|
const [pageSize, setPageSize] = useState(10);
|
|
17
17
|
const [sortModel, setSortModel] = useState<GridSortModel>(DEFAULT_SORT_MODEL);
|
|
18
18
|
const [filterModel, setFilterModel] = useState<GridFilterModel>(initialFilterModel ?? DEFAULT_FILTER_MODEL);
|
|
19
|
-
const [selectionModel, setSelectionModel] = useState<GridRowSelectionModel>(
|
|
19
|
+
const [selectionModel, setSelectionModel] = useState<GridRowSelectionModel>(EMPTY_ROW_SELECTION_MODEL);
|
|
20
20
|
|
|
21
21
|
const quickFilterText = (filterModel as { quickFilterValues?: string[] }).quickFilterValues?.join(' ') || '';
|
|
22
22
|
const debounceRef = useRef<ReturnType<typeof setTimeout>>();
|
|
@@ -54,7 +54,7 @@ export default ({ initialFilterModel }: { initialFilterModel?: GridFilterModel }
|
|
|
54
54
|
<div style={{ width: '100%' }}>
|
|
55
55
|
<Flexbox flexDirection="column" gap="0px">
|
|
56
56
|
<BulkActionBar
|
|
57
|
-
count={selectionModel
|
|
57
|
+
count={getSelectionCount(selectionModel)}
|
|
58
58
|
onLog={() => console.log('Selected:', selectionModel)}
|
|
59
59
|
onDelete={() => console.log('Delete:', selectionModel)}
|
|
60
60
|
/>
|
|
@@ -62,7 +62,7 @@ export default ({ initialFilterModel }: { initialFilterModel?: GridFilterModel }
|
|
|
62
62
|
<DataGrid
|
|
63
63
|
autoHeight
|
|
64
64
|
pagination
|
|
65
|
-
|
|
65
|
+
density="compact"
|
|
66
66
|
paginationMode="server"
|
|
67
67
|
sortingMode="server"
|
|
68
68
|
filterMode="server"
|
|
@@ -74,7 +74,6 @@ export default ({ initialFilterModel }: { initialFilterModel?: GridFilterModel }
|
|
|
74
74
|
pageSizeOptions={[10, 25, 50]}
|
|
75
75
|
onPaginationModelChange={(model) => {
|
|
76
76
|
if (model.pageSize !== pageSize) {
|
|
77
|
-
setRows([]);
|
|
78
77
|
setPageSize(model.pageSize);
|
|
79
78
|
setPage(0);
|
|
80
79
|
} else {
|