@redsift/ds-mcp-server 12.3.1-muiv6-alpha.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/README.md +203 -0
- package/consumer-instructions/.cursorrules +80 -0
- package/consumer-instructions/.windsurfrules +80 -0
- package/consumer-instructions/CLAUDE.md +87 -0
- package/consumer-instructions/redsift-design-system.instructions.md +87 -0
- package/data/demos/patterns/crossfiltered-datagrid-page/example.tsx +750 -0
- package/data/demos/patterns/crossfiltered-datagrid-page/with-empty-state.tsx +111 -0
- package/data/demos/patterns/crossfiltered-datagrid-page/with-error.tsx +122 -0
- package/data/demos/patterns/crossfiltered-datagrid-page/with-loading.tsx +88 -0
- package/data/demos/patterns/datagrid-page/example.tsx +521 -0
- package/data/demos/patterns/datagrid-page/with-empty-state.tsx +80 -0
- package/data/demos/patterns/datagrid-page/with-error.tsx +96 -0
- package/data/demos/patterns/datagrid-page/with-loading.tsx +57 -0
- package/data/demos/patterns/drilldown-datagrid-page/example.tsx +715 -0
- package/data/demos/patterns/drilldown-datagrid-page/with-empty-state.tsx +113 -0
- package/data/demos/patterns/drilldown-datagrid-page/with-error.tsx +125 -0
- package/data/demos/patterns/drilldown-datagrid-page/with-loading.tsx +90 -0
- package/data/demos/patterns/server-datagrid-page/example.tsx +757 -0
- package/data/demos/patterns/server-datagrid-page/with-empty-state.tsx +107 -0
- package/data/demos/patterns/server-datagrid-page/with-error.tsx +107 -0
- package/data/demos/patterns/server-datagrid-page/with-loading.tsx +63 -0
- package/data/docs/components/charts/Arc.json +179 -0
- package/data/docs/components/charts/Arcs.json +104 -0
- package/data/docs/components/charts/Axis.json +269 -0
- package/data/docs/components/charts/Bar.json +236 -0
- package/data/docs/components/charts/BarChart.json +852 -0
- package/data/docs/components/charts/BarChartBars.json +18 -0
- package/data/docs/components/charts/BarChartGroupedTooltip.json +9 -0
- package/data/docs/components/charts/BarChartOverlay.json +34 -0
- package/data/docs/components/charts/BarChartSection.json +94 -0
- package/data/docs/components/charts/BaseBarChart.json +852 -0
- package/data/docs/components/charts/ChartContainerTitle.json +574 -0
- package/data/docs/components/charts/DataPoint.json +160 -0
- package/data/docs/components/charts/Dot.json +191 -0
- package/data/docs/components/charts/EmptyBarChart.json +852 -0
- package/data/docs/components/charts/EmptyLineChart.json +753 -0
- package/data/docs/components/charts/EmptyPieChart.json +826 -0
- package/data/docs/components/charts/EmptyScatterPlot.json +802 -0
- package/data/docs/components/charts/Legend.json +510 -0
- package/data/docs/components/charts/LegendItem.json +128 -0
- package/data/docs/components/charts/Line.json +69 -0
- package/data/docs/components/charts/LineChart.json +753 -0
- package/data/docs/components/charts/LoadingBarChart.json +852 -0
- package/data/docs/components/charts/LoadingLineChart.json +753 -0
- package/data/docs/components/charts/LoadingPieChart.json +826 -0
- package/data/docs/components/charts/LoadingScatterPlot.json +802 -0
- package/data/docs/components/charts/PieChart.json +826 -0
- package/data/docs/components/charts/RenderedLineChart.json +753 -0
- package/data/docs/components/charts/RenderedLinearBarChart.json +823 -0
- package/data/docs/components/charts/RenderedOrdinalBarChart.json +852 -0
- package/data/docs/components/charts/RenderedPieChart.json +826 -0
- package/data/docs/components/charts/RenderedScatterPlot.json +802 -0
- package/data/docs/components/charts/ScatterPlot.json +802 -0
- package/data/docs/components/charts/getAxisType.json +9 -0
- package/data/docs/components/charts/getComponentPosition.json +9 -0
- package/data/docs/components/dashboard/ChartEmptyState.json +42 -0
- package/data/docs/components/dashboard/Dashboard.json +26 -0
- package/data/docs/components/dashboard/DataCard.json +300 -0
- package/data/docs/components/dashboard/DataCardBody.json +431 -0
- package/data/docs/components/dashboard/DataCardHeader.json +304 -0
- package/data/docs/components/dashboard/DataCardListbox.json +529 -0
- package/data/docs/components/dashboard/DataRow.json +539 -0
- package/data/docs/components/dashboard/DrilldownItem.json +342 -0
- package/data/docs/components/dashboard/FilterableBarChart.json +83 -0
- package/data/docs/components/dashboard/FilterableDataGrid.json +83 -0
- package/data/docs/components/dashboard/FilterablePieChart.json +83 -0
- package/data/docs/components/dashboard/FilterableScatterPlot.json +83 -0
- package/data/docs/components/dashboard/PdfDocument.json +58 -0
- package/data/docs/components/dashboard/PdfExportButton.json +458 -0
- package/data/docs/components/dashboard/TimeSeriesBarChart.json +172 -0
- package/data/docs/components/dashboard/WithFilters.json +83 -0
- package/data/docs/components/design-system/ActiveDescendantListbox.json +521 -0
- package/data/docs/components/design-system/Alert.json +349 -0
- package/data/docs/components/design-system/AppBar.json +64 -0
- package/data/docs/components/design-system/AppContainer.json +67 -0
- package/data/docs/components/design-system/AppContent.json +566 -0
- package/data/docs/components/design-system/AppSidePanel.json +87 -0
- package/data/docs/components/design-system/Badge.json +293 -0
- package/data/docs/components/design-system/BaseBreadcrumbs.json +298 -0
- package/data/docs/components/design-system/BaseGrid.json +543 -0
- package/data/docs/components/design-system/BaseSkeleton.json +338 -0
- package/data/docs/components/design-system/BreadcrumbItem.json +231 -0
- package/data/docs/components/design-system/Breadcrumbs.json +298 -0
- package/data/docs/components/design-system/Button.json +402 -0
- package/data/docs/components/design-system/ButtonGroup.json +415 -0
- package/data/docs/components/design-system/ButtonLink.json +568 -0
- package/data/docs/components/design-system/Card.json +328 -0
- package/data/docs/components/design-system/CardActions.json +431 -0
- package/data/docs/components/design-system/CardBody.json +431 -0
- package/data/docs/components/design-system/CardHeader.json +428 -0
- package/data/docs/components/design-system/Checkbox.json +426 -0
- package/data/docs/components/design-system/CheckboxGroup.json +382 -0
- package/data/docs/components/design-system/ConditionalWrapper.json +40 -0
- package/data/docs/components/design-system/DetailedCard.json +401 -0
- package/data/docs/components/design-system/DetailedCardCollapsibleSectionItems.json +29 -0
- package/data/docs/components/design-system/DetailedCardHeader.json +37 -0
- package/data/docs/components/design-system/DetailedCardSection.json +90 -0
- package/data/docs/components/design-system/DetailedCardSectionItem.json +109 -0
- package/data/docs/components/design-system/Flexbox.json +523 -0
- package/data/docs/components/design-system/FocusWithinGroup.json +9 -0
- package/data/docs/components/design-system/Grid.json +543 -0
- package/data/docs/components/design-system/GridItem.json +388 -0
- package/data/docs/components/design-system/Heading.json +390 -0
- package/data/docs/components/design-system/Icon.json +325 -0
- package/data/docs/components/design-system/IconButton.json +371 -0
- package/data/docs/components/design-system/IconButtonLink.json +588 -0
- package/data/docs/components/design-system/Item.json +554 -0
- package/data/docs/components/design-system/Link.json +552 -0
- package/data/docs/components/design-system/LinkButton.json +397 -0
- package/data/docs/components/design-system/Listbox.json +529 -0
- package/data/docs/components/design-system/Number.json +773 -0
- package/data/docs/components/design-system/NumberField.json +594 -0
- package/data/docs/components/design-system/Pill.json +378 -0
- package/data/docs/components/design-system/ProgressBar.json +121 -0
- package/data/docs/components/design-system/RadarSvgLinearGradient.json +9 -0
- package/data/docs/components/design-system/Radio.json +415 -0
- package/data/docs/components/design-system/RadioGroup.json +382 -0
- package/data/docs/components/design-system/RenderedListboxItem.json +18 -0
- package/data/docs/components/design-system/RovingTabindexListbox.json +521 -0
- package/data/docs/components/design-system/Shield.json +360 -0
- package/data/docs/components/design-system/SideNavigationMenu.json +144 -0
- package/data/docs/components/design-system/SideNavigationMenuBar.json +89 -0
- package/data/docs/components/design-system/SideNavigationMenuItem.json +319 -0
- package/data/docs/components/design-system/Skeleton.json +338 -0
- package/data/docs/components/design-system/SkeletonCircle.json +338 -0
- package/data/docs/components/design-system/SkeletonText.json +371 -0
- package/data/docs/components/design-system/Spinner.json +291 -0
- package/data/docs/components/design-system/Switch.json +415 -0
- package/data/docs/components/design-system/SwitchGroup.json +382 -0
- package/data/docs/components/design-system/Text.json +418 -0
- package/data/docs/components/design-system/TextArea.json +501 -0
- package/data/docs/components/design-system/TextField.json +539 -0
- package/data/docs/components/design-system/sizeToDimension.json +9 -0
- package/data/docs/components/pickers/BaseCombobox.json +320 -0
- package/data/docs/components/pickers/BaseComboboxContent.json +453 -0
- package/data/docs/components/pickers/BaseMenuButton.json +240 -0
- package/data/docs/components/pickers/BaseMenuButtonContent.json +442 -0
- package/data/docs/components/pickers/BaseSelect.json +258 -0
- package/data/docs/components/pickers/Combobox.json +320 -0
- package/data/docs/components/pickers/ComboboxContent.json +453 -0
- package/data/docs/components/pickers/ComboboxContentFooter.json +431 -0
- package/data/docs/components/pickers/ComboboxContentHeader.json +431 -0
- package/data/docs/components/pickers/ComboboxContentListbox.json +541 -0
- package/data/docs/components/pickers/ComboboxTrigger.json +336 -0
- package/data/docs/components/pickers/Item.json +554 -0
- package/data/docs/components/pickers/MenuButton.json +240 -0
- package/data/docs/components/pickers/MenuButtonContent.json +442 -0
- package/data/docs/components/pickers/MenuButtonContentFooter.json +431 -0
- package/data/docs/components/pickers/MenuButtonContentHeader.json +431 -0
- package/data/docs/components/pickers/MenuButtonContentMenu.json +523 -0
- package/data/docs/components/pickers/MenuButtonTrigger.json +287 -0
- package/data/docs/components/pickers/Select.json +258 -0
- package/data/docs/components/pickers/SelectContent.json +442 -0
- package/data/docs/components/pickers/SelectTrigger.json +298 -0
- package/data/docs/components/popovers/BaseDialog.json +114 -0
- package/data/docs/components/popovers/BaseDialogContent.json +21 -0
- package/data/docs/components/popovers/BasePopover.json +171 -0
- package/data/docs/components/popovers/BaseToggletip.json +184 -0
- package/data/docs/components/popovers/BaseTooltip.json +121 -0
- package/data/docs/components/popovers/Button.json +402 -0
- package/data/docs/components/popovers/ButtonLink.json +568 -0
- package/data/docs/components/popovers/Dialog.json +114 -0
- package/data/docs/components/popovers/DialogContent.json +21 -0
- package/data/docs/components/popovers/DialogContentActions.json +442 -0
- package/data/docs/components/popovers/DialogContentBody.json +442 -0
- package/data/docs/components/popovers/DialogContentHeader.json +76 -0
- package/data/docs/components/popovers/DialogTrigger.json +276 -0
- package/data/docs/components/popovers/IconButton.json +371 -0
- package/data/docs/components/popovers/IconButtonLink.json +588 -0
- package/data/docs/components/popovers/Link.json +552 -0
- package/data/docs/components/popovers/LinkButton.json +397 -0
- package/data/docs/components/popovers/Popover.json +171 -0
- package/data/docs/components/popovers/PopoverContent.json +442 -0
- package/data/docs/components/popovers/PopoverTrigger.json +276 -0
- package/data/docs/components/popovers/Toast.json +145 -0
- package/data/docs/components/popovers/ToastContainer.json +122 -0
- package/data/docs/components/popovers/Toggletip.json +184 -0
- package/data/docs/components/popovers/ToggletipContent.json +402 -0
- package/data/docs/components/popovers/ToggletipTrigger.json +276 -0
- package/data/docs/components/popovers/Tooltip.json +121 -0
- package/data/docs/components/popovers/TooltipContent.json +402 -0
- package/data/docs/components/popovers/TooltipTrigger.json +276 -0
- package/data/docs/components/products/Dialog.json +106 -0
- package/data/docs/components/products/MenuButton.json +232 -0
- package/data/docs/components/products/PulsedRadarImage.json +9 -0
- package/data/docs/components/products/RadarButton.json +402 -0
- package/data/docs/components/products/RadarItem.json +554 -0
- package/data/docs/components/table/ControlledPagination.json +9 -0
- package/data/docs/components/table/DataGrid.json +93 -0
- package/data/docs/components/table/GridToolbarFilterSemanticField.json +69 -0
- package/data/docs/components/table/ServerSideControlledPagination.json +9 -0
- package/data/docs/components/table/StatefulDataGrid.json +117 -0
- package/data/docs/components/table/TextCell.json +118 -0
- package/data/docs/components/table/Toolbar.json +145 -0
- package/data/docs/components/table/ToolbarWrapper.json +9 -0
- package/data/docs/components-index.json +1206 -0
- package/data/docs/components.json +55694 -0
- package/data/docs/llms-full.txt +8012 -0
- package/data/docs/llms.txt +234 -0
- package/data/docs/patterns-catalog.md +359 -0
- package/data/docs/patterns.json +712 -0
- package/data/metadata.json +4 -0
- package/data/patterns/crossfiltered-datagrid-page.mdx +386 -0
- package/data/patterns/datagrid-page.mdx +214 -0
- package/data/patterns/drilldown-datagrid-page.mdx +291 -0
- package/data/patterns/server-datagrid-page.mdx +301 -0
- package/data/tokens/properties/components/dark-components.json +1108 -0
- package/data/tokens/properties/components/light-components.json +1108 -0
- package/data/tokens/properties/core/border-radius.json +3 -0
- package/data/tokens/properties/core/color.json +280 -0
- package/data/tokens/properties/core/layout.json +14 -0
- package/data/tokens/properties/core/typography.json +199 -0
- package/data/tokens/redsift-design-tokens.css +1391 -0
- package/dist/data-store.d.ts +47 -0
- package/dist/data-store.d.ts.map +1 -0
- package/dist/data-store.js +152 -0
- package/dist/data-store.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +50 -0
- package/dist/index.js.map +1 -0
- package/dist/init.d.ts +14 -0
- package/dist/init.d.ts.map +1 -0
- package/dist/init.js +137 -0
- package/dist/init.js.map +1 -0
- package/dist/paths.d.ts +30 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +53 -0
- package/dist/paths.js.map +1 -0
- package/dist/pattern-store.d.ts +41 -0
- package/dist/pattern-store.d.ts.map +1 -0
- package/dist/pattern-store.js +177 -0
- package/dist/pattern-store.js.map +1 -0
- package/dist/prompts.d.ts +14 -0
- package/dist/prompts.d.ts.map +1 -0
- package/dist/prompts.js +762 -0
- package/dist/prompts.js.map +1 -0
- package/dist/resources.d.ts +14 -0
- package/dist/resources.d.ts.map +1 -0
- package/dist/resources.js +482 -0
- package/dist/resources.js.map +1 -0
- package/dist/scaffold.d.ts +31 -0
- package/dist/scaffold.d.ts.map +1 -0
- package/dist/scaffold.js +239 -0
- package/dist/scaffold.js.map +1 -0
- package/dist/token-store.d.ts +70 -0
- package/dist/token-store.d.ts.map +1 -0
- package/dist/token-store.js +196 -0
- package/dist/token-store.js.map +1 -0
- package/dist/tools.d.ts +15 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +491 -0
- package/dist/tools.js.map +1 -0
- package/dist/types.d.ts +108 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +17 -0
- package/dist/types.js.map +1 -0
- package/package.json +39 -0
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
import { Link } from '@redsift/design-system';
|
|
2
|
+
import { DemoBlock } from '../../components/DemoBlock';
|
|
3
|
+
import { CodeBlock } from '../../components/CodeBlock';
|
|
4
|
+
import { FeatureTable } from '../../components/FeatureTable';
|
|
5
|
+
import { RouterLink } from '../../components/RouterLink';
|
|
6
|
+
|
|
7
|
+
## Introduction
|
|
8
|
+
|
|
9
|
+
The Drilldown Datagrid Page extends the standard [Datagrid Page](/patterns/datagrid-page) with summary **DataCards** placed above the grid. Each DataCard contains **DrilldownItems** that display aggregate counts for a field (e.g. status, category). Clicking a DrilldownItem adds an `isAnyOf` filter to the DataGrid's filter panel — a one-click shortcut for filtering by that value.
|
|
10
|
+
|
|
11
|
+
This is a **one-way** interaction: DrilldownItem clicks push filters into the grid, but the DataCard counts always reflect the **full** (unfiltered) dataset. If you need DataCard counts to recalculate as filters change, use the [Cross-filtered Datagrid Page](/patterns/crossfiltered-datagrid-page) instead.
|
|
12
|
+
|
|
13
|
+
When the user sets an `is` or `isNot` filter via the DataGrid filter panel (instead of clicking a DrilldownItem), the DataCard selection **clears** — all DrilldownItems are deselected. Clicking a DrilldownItem after that overwrites the incompatible filter with a fresh `isAnyOf` item.
|
|
14
|
+
|
|
15
|
+
## When to Use
|
|
16
|
+
|
|
17
|
+
- You want **summary KPI cards** above a filterable table
|
|
18
|
+
- Clicking a card value should **add a filter** to the datagrid as a convenience shortcut
|
|
19
|
+
- DataCard counts should always show totals from the **full** dataset, regardless of active filters
|
|
20
|
+
- The dataset is small enough to load entirely (under ~1,000 rows)
|
|
21
|
+
|
|
22
|
+
When **not** to use:
|
|
23
|
+
|
|
24
|
+
- DataCard counts should **recalculate** when filters change — use [Cross-filtered Datagrid Page](/patterns/crossfiltered-datagrid-page) instead
|
|
25
|
+
- No summary cards are needed — use [Datagrid Page](/patterns/datagrid-page) instead
|
|
26
|
+
- The dataset is too large to load entirely — combine with [Server Datagrid Page](/patterns/server-datagrid-page) patterns
|
|
27
|
+
|
|
28
|
+
## Anatomy
|
|
29
|
+
|
|
30
|
+
1. **DataCard Row** — `Flexbox` with 1–N `DataCard` components, each containing a `DataCard.Header` and either `DataCard.Listbox` (for multi-select fields) or `DataCard.Body` (for boolean toggles) with `DrilldownItem` children
|
|
31
|
+
2. **Toolbar** — `Flexbox` wrapping MUI's `GridToolbarContainer` with `GridToolbarFilterButton`, `GridToolbarColumnsButton`, `GridToolbarDensitySelector`, `GridToolbarExport`, and `GridToolbarQuickFilter`
|
|
32
|
+
3. **DataGrid** — the table itself with `filterModel` + `onFilterModelChange` controlled state
|
|
33
|
+
4. **Bulk Action Bar** — a `Flexbox` row shown when `selectionModel.length > 0`, containing a `Pill` with the selection count and action `Button` components
|
|
34
|
+
|
|
35
|
+
## Features
|
|
36
|
+
|
|
37
|
+
<FeatureTable
|
|
38
|
+
features={[
|
|
39
|
+
{
|
|
40
|
+
feature: 'DataCard aggregation',
|
|
41
|
+
required: true,
|
|
42
|
+
description: (
|
|
43
|
+
<>
|
|
44
|
+
<code>DataCard</code> components showing aggregate counts per field from the full dataset
|
|
45
|
+
</>
|
|
46
|
+
),
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
feature: 'DrilldownItem filter push',
|
|
50
|
+
required: true,
|
|
51
|
+
description: (
|
|
52
|
+
<>
|
|
53
|
+
Clicking a <code>DrilldownItem</code> adds an <code>isAnyOf</code> filter to the DataGrid's{' '}
|
|
54
|
+
<code>filterModel</code>
|
|
55
|
+
</>
|
|
56
|
+
),
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
feature: 'Listbox multi-select',
|
|
60
|
+
required: true,
|
|
61
|
+
description: (
|
|
62
|
+
<>
|
|
63
|
+
<code>DataCard.Listbox</code> with <code>{'selectionMode="multiple"'}</code> and controlled{' '}
|
|
64
|
+
<code>values</code> for multi-value fields
|
|
65
|
+
</>
|
|
66
|
+
),
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
feature: (
|
|
70
|
+
<>
|
|
71
|
+
<code>is</code>/<code>isNot</code> clearing
|
|
72
|
+
</>
|
|
73
|
+
),
|
|
74
|
+
required: true,
|
|
75
|
+
description: (
|
|
76
|
+
<>
|
|
77
|
+
When filter panel has <code>is</code>/<code>isNot</code> for a card's field, the DataCard selection clears
|
|
78
|
+
</>
|
|
79
|
+
),
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
feature: 'Column definitions',
|
|
83
|
+
required: true,
|
|
84
|
+
description: (
|
|
85
|
+
<>
|
|
86
|
+
<code>GridColDef[]</code> with custom types: <code>rsString</code>, <code>rsNumber</code>,{' '}
|
|
87
|
+
<code>rsSingleSelect</code>, <code>rsMultipleSelect</code>
|
|
88
|
+
</>
|
|
89
|
+
),
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
feature: 'Toolbar',
|
|
93
|
+
required: true,
|
|
94
|
+
description: 'Built-in toolbar with filter, columns, density, export, and quick search controls',
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
feature: 'Custom cell renderers',
|
|
98
|
+
required: true,
|
|
99
|
+
description: (
|
|
100
|
+
<>
|
|
101
|
+
<code>renderCell</code> returning <code>TextCell</code>, <code>Pill</code>, <code>Icon</code>,{' '}
|
|
102
|
+
<code>IconButtonLink</code>
|
|
103
|
+
</>
|
|
104
|
+
),
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
feature: 'Boolean toggle',
|
|
108
|
+
required: false,
|
|
109
|
+
description: (
|
|
110
|
+
<>
|
|
111
|
+
<code>DataCard.Body</code> with <code>DrilldownItem</code> <code>onClick</code> for boolean fields
|
|
112
|
+
(active/inactive)
|
|
113
|
+
</>
|
|
114
|
+
),
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
feature: 'Checkbox selection',
|
|
118
|
+
required: false,
|
|
119
|
+
description: (
|
|
120
|
+
<>
|
|
121
|
+
<code>checkboxSelection</code> + <code>rowSelectionModel</code>
|
|
122
|
+
</>
|
|
123
|
+
),
|
|
124
|
+
},
|
|
125
|
+
{ feature: 'Bulk action bar', required: false, description: 'Contextual buttons shown when rows are selected' },
|
|
126
|
+
]}
|
|
127
|
+
/>
|
|
128
|
+
|
|
129
|
+
### Components
|
|
130
|
+
|
|
131
|
+
{/* prettier-ignore */}
|
|
132
|
+
- <Link href="/table/datagrid" as={RouterLink}>DataGrid</Link> — the table itself (from `@redsift/table`)
|
|
133
|
+
- <Link href="/dashboard/data-card" as={RouterLink}>DataCard</Link> — summary card container with colored border (from `@redsift/dashboard`)
|
|
134
|
+
- <Link href="/dashboard/data-card" as={RouterLink}>DataCard.Header</Link> — card header with icon and title
|
|
135
|
+
- <Link href="/dashboard/data-card" as={RouterLink}>DataCard.Body</Link> — static row container for DataRow or DrilldownItem
|
|
136
|
+
- <Link href="/dashboard/data-card" as={RouterLink}>DataCard.Listbox</Link> — multi-select container synced with DataGrid filters
|
|
137
|
+
- <Link href="/dashboard/drilldown-item" as={RouterLink}>DrilldownItem</Link> — numeric value with legend and filter icon (from `@redsift/dashboard`)
|
|
138
|
+
- <Link href="/layout/flexbox" as={RouterLink}>Flexbox</Link> — page and toolbar layout
|
|
139
|
+
- <Link href="/forms/button" as={RouterLink}>Button</Link> — toolbar and bulk action buttons
|
|
140
|
+
- <Link href="/forms/icon-button-link" as={RouterLink}>IconButtonLink</Link> — action column navigation links
|
|
141
|
+
- <Link href="/data-display/pill" as={RouterLink}>Pill</Link> — status badges in cells and count indicators
|
|
142
|
+
- <Link href="/typography/text" as={RouterLink}>Text</Link> — cell content and labels
|
|
143
|
+
- <Link href="/media-and-icons/icon" as={RouterLink}>Icon</Link> — toolbar icons and cell indicators
|
|
144
|
+
|
|
145
|
+
## State Management
|
|
146
|
+
|
|
147
|
+
<CodeBlock codeString={`import { useCallback, useMemo, useState } from 'react';
|
|
148
|
+
import { GridFilterModel, GridSelectionModel } from '@mui/x-data-grid-pro';
|
|
149
|
+
|
|
150
|
+
// Filter state — controlled, shared between DataCards and DataGrid
|
|
151
|
+
const [filterModel, setFilterModel] = useState<GridFilterModel>({ items: [] });
|
|
152
|
+
|
|
153
|
+
// Selection state
|
|
154
|
+
const [selectionModel, setSelectionModel] = useState<GridSelectionModel>([]);
|
|
155
|
+
|
|
156
|
+
// Aggregate counts — always computed from the FULL dataset (one-way)
|
|
157
|
+
const counts = useAggregateCounts(allRows);
|
|
158
|
+
|
|
159
|
+
// Derive DataCard selections from filterModel (returns [] for is/isNot)
|
|
160
|
+
const statusSelection = getSelectedFromFilterModel(filterModel, 'status');
|
|
161
|
+
const categorySelection = getSelectedFromFilterModel(filterModel, 'category');
|
|
162
|
+
|
|
163
|
+
// DataCard change handler — always writes isAnyOf filters
|
|
164
|
+
const handleStatusChange = useCallback(
|
|
165
|
+
(values: string[]) => setFilterModel((prev) => updateFilterModel(prev, 'status', values)),
|
|
166
|
+
[],
|
|
167
|
+
);
|
|
168
|
+
`} />
|
|
169
|
+
|
|
170
|
+
## Filter Helpers
|
|
171
|
+
|
|
172
|
+
<CodeBlock codeString={`// Read selected values for a field — only syncs with isAnyOf.
|
|
173
|
+
// Returns [] for is/isNot operators (clears the DataCard).
|
|
174
|
+
function getSelectedFromFilterModel(filterModel: GridFilterModel, fieldName: string): string[] {
|
|
175
|
+
const item = filterModel.items.find((i) => i.field === fieldName);
|
|
176
|
+
if (!item || item.value === undefined) return [];
|
|
177
|
+
if (item.operator === 'isAnyOf') {
|
|
178
|
+
return Array.isArray(item.value) ? item.value : [item.value];
|
|
179
|
+
}
|
|
180
|
+
return []; // is, isNot, or other → clear card
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Write an isAnyOf filter — always creates a fresh item, never preserves
|
|
184
|
+
// an incompatible operator. Empty values removes the filter entirely.
|
|
185
|
+
function updateFilterModel(
|
|
186
|
+
filterModel: GridFilterModel,
|
|
187
|
+
fieldName: string,
|
|
188
|
+
values: string[],
|
|
189
|
+
): GridFilterModel {
|
|
190
|
+
const items = filterModel.items.filter((i) => i.field !== fieldName);
|
|
191
|
+
if (values.length > 0) {
|
|
192
|
+
items.push({
|
|
193
|
+
field: fieldName,
|
|
194
|
+
id: Math.floor(Math.random() \* 100000),
|
|
195
|
+
operator: 'isAnyOf',
|
|
196
|
+
value: values,
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
return { ...filterModel, items };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Boolean filter helper — uses the 'is' operator with 'true'/'false'.
|
|
203
|
+
function updateBooleanFilter(
|
|
204
|
+
filterModel: GridFilterModel,
|
|
205
|
+
fieldName: string,
|
|
206
|
+
value: boolean | null,
|
|
207
|
+
): GridFilterModel {
|
|
208
|
+
const items = filterModel.items.filter((i) => i.field !== fieldName);
|
|
209
|
+
if (value !== null) {
|
|
210
|
+
items.push({
|
|
211
|
+
field: fieldName,
|
|
212
|
+
id: Math.floor(Math.random() \* 100000),
|
|
213
|
+
operator: 'is',
|
|
214
|
+
value: String(value),
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
return { ...filterModel, items };
|
|
218
|
+
}
|
|
219
|
+
`} />
|
|
220
|
+
|
|
221
|
+
## Data Contract
|
|
222
|
+
|
|
223
|
+
<CodeBlock codeString={`// Row type — same as Datagrid Page. Every row MUST have a unique 'id' field.
|
|
224
|
+
type Row = {
|
|
225
|
+
id: string;
|
|
226
|
+
label: string;
|
|
227
|
+
active: boolean;
|
|
228
|
+
status: 'active' | 'warning' | 'critical' | 'expired' | 'revoked';
|
|
229
|
+
category: 'primary' | 'secondary' | 'tertiary';
|
|
230
|
+
score: number;
|
|
231
|
+
expiry: string;
|
|
232
|
+
tags: string[];
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
// Aggregate counts computed from the full dataset
|
|
236
|
+
type AggregateCounts = {
|
|
237
|
+
status: Record<string, number>;
|
|
238
|
+
category: Record<string, number>;
|
|
239
|
+
activeCount: number;
|
|
240
|
+
inactiveCount: number;
|
|
241
|
+
};
|
|
242
|
+
`} />
|
|
243
|
+
|
|
244
|
+
## Example
|
|
245
|
+
|
|
246
|
+
The same 27-row dataset as the Datagrid Page, with three DataCards above: **Status** (5 DrilldownItems), **Category** (3 DrilldownItems), and **Active** (boolean toggle with 2 DrilldownItems). Clicking a DrilldownItem adds an `isAnyOf` filter to the grid. The Status and Category cards use `DataCard.Listbox` with controlled `values` for multi-select. The Active card uses `DataCard.Body` with `onClick` toggles. DataCard counts always show the full 27-row totals.
|
|
247
|
+
|
|
248
|
+
<DemoBlock withThemeSwitcher path="patterns/drilldown-datagrid-page/example" />
|
|
249
|
+
|
|
250
|
+
## Loading State
|
|
251
|
+
|
|
252
|
+
DataCards with `isLoading={true}` (hides headers, shows skeleton placeholders) alongside a DataGrid with `loading={true}` and `rows={[]}`. Use this state while fetching initial data.
|
|
253
|
+
|
|
254
|
+
<DemoBlock withThemeSwitcher path="patterns/drilldown-datagrid-page/with-loading" />
|
|
255
|
+
|
|
256
|
+
## Empty State
|
|
257
|
+
|
|
258
|
+
DataCards showing zero counts on all DrilldownItems, with the DataGrid displaying a custom `NoRowsOverlay` with "No results match your filters" and guidance to adjust filters.
|
|
259
|
+
|
|
260
|
+
<DemoBlock withThemeSwitcher path="patterns/drilldown-datagrid-page/with-empty-state" />
|
|
261
|
+
|
|
262
|
+
## Error State
|
|
263
|
+
|
|
264
|
+
An error banner (`role="alert"`) above the DataCards and DataGrid with a retry button. DataCards switch to loading state during retry. The page layout remains intact.
|
|
265
|
+
|
|
266
|
+
<DemoBlock withThemeSwitcher path="patterns/drilldown-datagrid-page/with-error" />
|
|
267
|
+
|
|
268
|
+
## Implementation Checklist
|
|
269
|
+
|
|
270
|
+
1. **Define your row type** — Create a TypeScript `type Row` per the Data Contract section. Every row must have a unique `id` field.
|
|
271
|
+
2. **Compute aggregate counts** — Write a `useAggregateCounts(allRows)` hook that counts occurrences per field value from the full dataset. This never recalculates when filters change.
|
|
272
|
+
3. **Build DataCards** — Create a `DataCard` for each summarized field with `DataCard.Header` (icon + title) and either `DataCard.Listbox` (multi-select) or `DataCard.Body` (boolean toggle) containing `DrilldownItem` children.
|
|
273
|
+
4. **Write filter helpers** — Implement `getSelectedFromFilterModel` (reads `isAnyOf` values, returns `[]` for `is`/`isNot`) and `updateFilterModel` (always writes `isAnyOf`, removes when empty).
|
|
274
|
+
5. **Wire DataCard handlers** — Connect `DataCard.Listbox` `onChange` to `updateFilterModel`. For boolean cards, connect `DrilldownItem` `onClick` to `updateBooleanFilter`.
|
|
275
|
+
6. **Render the DataGrid** — Pass `filterModel` and `onFilterModelChange={setFilterModel}` so the filter panel stays synced with DataCard selections.
|
|
276
|
+
7. **Handle edge cases** — Add loading (DataCards `isLoading` + DataGrid `loading`), empty (zero counts + `NoRowsOverlay`), and error states (banner + retry).
|
|
277
|
+
|
|
278
|
+
## Keyboard & Accessibility
|
|
279
|
+
|
|
280
|
+
- **DrilldownItem** renders as a `<button>` — focusable via **Tab**, activates with **Enter** or **Space**
|
|
281
|
+
- **DataCard.Listbox** supports **Arrow Up/Down** to navigate between items and **Space** to toggle selection
|
|
282
|
+
- When a DrilldownItem is selected, it receives `aria-selected="true"` from the Listbox
|
|
283
|
+
- DataGrid keyboard navigation works unchanged (arrows, Page Up/Down, Space for checkboxes)
|
|
284
|
+
- Error banners use `role="alert"` and `aria-live="polite"` for screen reader announcement
|
|
285
|
+
|
|
286
|
+
## Related Patterns
|
|
287
|
+
|
|
288
|
+
{/* prettier-ignore */}
|
|
289
|
+
- <Link href="/patterns/crossfiltered-datagrid-page" as={RouterLink}>Cross-filtered Datagrid Page</Link> — use when DataCard counts should recalculate as filters change (two-way sync)
|
|
290
|
+
- <Link href="/patterns/datagrid-page" as={RouterLink}>Datagrid Page</Link> — use when no summary cards are needed
|
|
291
|
+
- <Link href="/patterns/server-datagrid-page" as={RouterLink}>Server Datagrid Page</Link> — use when the dataset is too large to load entirely
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
import { Link } from '@redsift/design-system';
|
|
2
|
+
import { DemoBlock } from '../../components/DemoBlock';
|
|
3
|
+
import { CodeBlock } from '../../components/CodeBlock';
|
|
4
|
+
import { FeatureTable } from '../../components/FeatureTable';
|
|
5
|
+
import { RouterLink } from '../../components/RouterLink';
|
|
6
|
+
|
|
7
|
+
## Introduction
|
|
8
|
+
|
|
9
|
+
The Server Datagrid Page is a variant of the standard [Datagrid Page](/patterns/datagrid-page) where **all filtering, sorting, and pagination are handled server-side**. Instead of loading every row into the browser, only the current page of results is fetched from the server, and every user interaction (changing page, sorting a column, adding a filter) triggers a new request.
|
|
10
|
+
|
|
11
|
+
Use this pattern when the dataset is too large to load entirely — thousands or millions of records — or when the canonical sort/filter logic must live on the backend (e.g. ElasticSearch queries, SQL `ORDER BY`).
|
|
12
|
+
|
|
13
|
+
The DataGrid is configured with `paginationMode="server"`, `sortingMode="server"`, and `filterMode="server"`. A controlled `rowCount` tells the grid the total number of matching records so it can render the correct number of pagination pages. The `loading` prop activates the built-in loading overlay during fetches.
|
|
14
|
+
|
|
15
|
+
## When to Use
|
|
16
|
+
|
|
17
|
+
- The dataset has **thousands or millions of records** that cannot be loaded into the browser
|
|
18
|
+
- Filtering, sorting, or pagination logic must live on the **backend** (ElasticSearch, SQL, API)
|
|
19
|
+
- You need the server to return the **total matching count** alongside the current page of results
|
|
20
|
+
- Filter changes should be **debounced** to avoid excessive requests during rapid typing
|
|
21
|
+
|
|
22
|
+
When **not** to use:
|
|
23
|
+
|
|
24
|
+
- The dataset is small enough to load entirely (under ~1,000 rows) — use [Datagrid Page](/patterns/datagrid-page) instead
|
|
25
|
+
- All data is available upfront and filtering can happen in the browser — use [Datagrid Page](/patterns/datagrid-page) instead
|
|
26
|
+
|
|
27
|
+
## Anatomy
|
|
28
|
+
|
|
29
|
+
1. **Toolbar** — `Flexbox` wrapping MUI's `GridToolbarContainer` with `GridToolbarFilterButton`, `GridToolbarColumnsButton`, `GridToolbarDensitySelector`, `GridToolbarExport`, and `GridToolbarQuickFilter`
|
|
30
|
+
2. **DataGrid** — the table configured with `paginationMode="server"`, `sortingMode="server"`, `filterMode="server"`, `loading`, and `rowCount`
|
|
31
|
+
3. **Bulk Action Bar** — a `Flexbox` row shown when `selectionModel.length > 0`, containing a `Pill` with the selection count and action `Button` components
|
|
32
|
+
|
|
33
|
+
## Features
|
|
34
|
+
|
|
35
|
+
<FeatureTable
|
|
36
|
+
features={[
|
|
37
|
+
{
|
|
38
|
+
feature: 'Server-side pagination',
|
|
39
|
+
required: true,
|
|
40
|
+
description: (
|
|
41
|
+
<>
|
|
42
|
+
<code>{'paginationMode="server"'}</code> with controlled <code>page</code>, <code>pageSize</code>,{' '}
|
|
43
|
+
<code>rowCount</code>
|
|
44
|
+
</>
|
|
45
|
+
),
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
feature: 'Server-side sorting',
|
|
49
|
+
required: true,
|
|
50
|
+
description: (
|
|
51
|
+
<>
|
|
52
|
+
<code>{'sortingMode="server"'}</code> with controlled <code>sortModel</code> + <code>onSortModelChange</code>
|
|
53
|
+
</>
|
|
54
|
+
),
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
feature: 'Server-side filtering',
|
|
58
|
+
required: true,
|
|
59
|
+
description: (
|
|
60
|
+
<>
|
|
61
|
+
<code>{'filterMode="server"'}</code> with controlled <code>filterModel</code> +{' '}
|
|
62
|
+
<code>onFilterModelChange</code>
|
|
63
|
+
</>
|
|
64
|
+
),
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
feature: 'Loading overlay',
|
|
68
|
+
required: true,
|
|
69
|
+
description: (
|
|
70
|
+
<>
|
|
71
|
+
<code>loading</code> prop set to <code>true</code> during fetches
|
|
72
|
+
</>
|
|
73
|
+
),
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
feature: 'Row count',
|
|
77
|
+
required: true,
|
|
78
|
+
description: (
|
|
79
|
+
<>
|
|
80
|
+
<code>rowCount</code> prop set to total matching records from the server response
|
|
81
|
+
</>
|
|
82
|
+
),
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
feature: 'Debounced filter changes',
|
|
86
|
+
required: true,
|
|
87
|
+
description: (
|
|
88
|
+
<>
|
|
89
|
+
300ms debounce on <code>onFilterModelChange</code> to avoid excessive requests
|
|
90
|
+
</>
|
|
91
|
+
),
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
feature: 'Column definitions',
|
|
95
|
+
required: true,
|
|
96
|
+
description: (
|
|
97
|
+
<>
|
|
98
|
+
<code>GridColDef[]</code> with custom types: <code>rsString</code>, <code>rsNumber</code>,{' '}
|
|
99
|
+
<code>rsSingleSelect</code>, <code>rsMultipleSelect</code>
|
|
100
|
+
</>
|
|
101
|
+
),
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
feature: 'Toolbar',
|
|
105
|
+
required: true,
|
|
106
|
+
description: 'Built-in toolbar with filter, columns, density, export, and quick search',
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
feature: 'Custom cell renderers',
|
|
110
|
+
required: true,
|
|
111
|
+
description: (
|
|
112
|
+
<>
|
|
113
|
+
<code>renderCell</code> returning <code>TextCell</code>, <code>Pill</code>, <code>Icon</code>,{' '}
|
|
114
|
+
<code>IconButtonLink</code>
|
|
115
|
+
</>
|
|
116
|
+
),
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
feature: 'Checkbox selection',
|
|
120
|
+
required: false,
|
|
121
|
+
description: (
|
|
122
|
+
<>
|
|
123
|
+
<code>checkboxSelection</code> + <code>rowSelectionModel</code>
|
|
124
|
+
</>
|
|
125
|
+
),
|
|
126
|
+
},
|
|
127
|
+
{ feature: 'Bulk action bar', required: false, description: 'Contextual buttons shown when rows are selected' },
|
|
128
|
+
{
|
|
129
|
+
feature: 'Quick search',
|
|
130
|
+
required: false,
|
|
131
|
+
description: (
|
|
132
|
+
<>
|
|
133
|
+
<code>GridToolbarQuickFilter</code> with server-side text search
|
|
134
|
+
</>
|
|
135
|
+
),
|
|
136
|
+
},
|
|
137
|
+
]}
|
|
138
|
+
/>
|
|
139
|
+
|
|
140
|
+
### Components
|
|
141
|
+
|
|
142
|
+
{/* prettier-ignore */}
|
|
143
|
+
- <Link href="/table/datagrid" as={RouterLink}>DataGrid</Link> — the table itself (from `@redsift/table`)
|
|
144
|
+
- <Link href="/layout/flexbox" as={RouterLink}>Flexbox</Link> — page and toolbar layout
|
|
145
|
+
- <Link href="/forms/button" as={RouterLink}>Button</Link> — toolbar and bulk action buttons
|
|
146
|
+
- <Link href="/forms/icon-button" as={RouterLink}>IconButton</Link> — toolbar icon controls (columns, filters, density, export, reset, save)
|
|
147
|
+
- <Link href="/forms/icon-button-link" as={RouterLink}>IconButtonLink</Link> — action column navigation links
|
|
148
|
+
- <Link href="/data-display/pill" as={RouterLink}>Pill</Link> — status badges in cells and count indicators
|
|
149
|
+
- <Link href="/typography/text" as={RouterLink}>Text</Link> — cell content, labels, and search placeholder
|
|
150
|
+
- <Link href="/media-and-icons/icon" as={RouterLink}>Icon</Link> — toolbar icons and cell indicators
|
|
151
|
+
- <Link href="/data-display/badge" as={RouterLink}>Badge</Link> — active filter count on the filter button
|
|
152
|
+
|
|
153
|
+
## State Management
|
|
154
|
+
|
|
155
|
+
<CodeBlock codeString={`import { useCallback, useEffect, useRef, useState } from 'react';
|
|
156
|
+
import { GridFilterModel, GridSelectionModel, GridSortModel } from '@mui/x-data-grid-pro';
|
|
157
|
+
|
|
158
|
+
// Data state — rows returned from the server for the current page
|
|
159
|
+
const [rows, setRows] = useState<Row[]>([]);
|
|
160
|
+
const [totalRows, setTotalRows] = useState(0);
|
|
161
|
+
const [loading, setLoading] = useState(true);
|
|
162
|
+
|
|
163
|
+
// Pagination state
|
|
164
|
+
const [page, setPage] = useState(0);
|
|
165
|
+
const [pageSize, setPageSize] = useState(10);
|
|
166
|
+
|
|
167
|
+
// Sort & filter state (controlled — sent to server on change)
|
|
168
|
+
const [sortModel, setSortModel] = useState<GridSortModel>([]);
|
|
169
|
+
const [filterModel, setFilterModel] = useState<GridFilterModel>({ items: [] });
|
|
170
|
+
|
|
171
|
+
// Selection state
|
|
172
|
+
const [selectionModel, setSelectionModel] = useState<GridSelectionModel>([]);
|
|
173
|
+
|
|
174
|
+
// Quick search
|
|
175
|
+
const [quickFilterText, setQuickFilterText] = useState('');
|
|
176
|
+
|
|
177
|
+
// Debounce ref for filter changes
|
|
178
|
+
const debounceRef = useRef<ReturnType<typeof setTimeout>>();
|
|
179
|
+
|
|
180
|
+
// Fetch data whenever page, pageSize, sortModel, or filterModel changes
|
|
181
|
+
const fetchData = useCallback(async () => {
|
|
182
|
+
setLoading(true);
|
|
183
|
+
const result = await yourFetchFunction({ page, pageSize, sortModel, filterModel, quickFilterText });
|
|
184
|
+
setRows(result.rows);
|
|
185
|
+
setTotalRows(result.totalRows);
|
|
186
|
+
setLoading(false);
|
|
187
|
+
}, [page, pageSize, sortModel, filterModel, quickFilterText]);
|
|
188
|
+
|
|
189
|
+
useEffect(() => { fetchData(); }, [fetchData]);
|
|
190
|
+
|
|
191
|
+
// Debounced filter handler (300ms)
|
|
192
|
+
const handleFilterChange = useCallback((model: GridFilterModel) => {
|
|
193
|
+
clearTimeout(debounceRef.current);
|
|
194
|
+
debounceRef.current = setTimeout(() => {
|
|
195
|
+
setFilterModel(model);
|
|
196
|
+
setPage(0); // Reset to first page on filter change
|
|
197
|
+
}, 300);
|
|
198
|
+
}, []);
|
|
199
|
+
`} />
|
|
200
|
+
|
|
201
|
+
## Data Contract
|
|
202
|
+
|
|
203
|
+
<CodeBlock codeString={`// Row type — adapt fields to your domain
|
|
204
|
+
// Every row MUST have a unique 'id' field (string or number)
|
|
205
|
+
type Row = {
|
|
206
|
+
id: string;
|
|
207
|
+
label: string;
|
|
208
|
+
active: boolean;
|
|
209
|
+
status: 'active' | 'warning' | 'critical' | 'expired';
|
|
210
|
+
category: string;
|
|
211
|
+
score: number;
|
|
212
|
+
expiry: string; // ISO 8601 date string
|
|
213
|
+
tags: string[];
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
// Fetch function parameters — sent to the server on every interaction
|
|
217
|
+
type FetchParams = {
|
|
218
|
+
page: number; // 0-based page index
|
|
219
|
+
pageSize: number; // rows per page (10, 25, 50)
|
|
220
|
+
sortModel: GridSortModel;
|
|
221
|
+
filterModel: GridFilterModel;
|
|
222
|
+
quickFilterText: string;
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
// Fetch function response — returned by the server
|
|
226
|
+
type FetchResult = {
|
|
227
|
+
rows: Row[]; // only the current page of results
|
|
228
|
+
totalRows: number; // total matching records (for pagination)
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
// Fetch function signature
|
|
232
|
+
async function fetchData(params: FetchParams): Promise<FetchResult> {
|
|
233
|
+
// Implement: translate params to your API/ElasticSearch query
|
|
234
|
+
// Return: { rows, totalRows }
|
|
235
|
+
}
|
|
236
|
+
`} />
|
|
237
|
+
|
|
238
|
+
## Example
|
|
239
|
+
|
|
240
|
+
The same 27-row dataset as the client-side Datagrid Page, but every interaction is routed through a mock `fakeFetch` function that simulates a 400 ms network round-trip. The mock applies filtering (string, number, date, single-select, multi-select, and boolean operators), sorting, and pagination server-side, then returns only the requested page slice alongside the total matching row count. Filter changes are debounced at 300 ms so rapid typing doesn't fire excessive requests.
|
|
241
|
+
|
|
242
|
+
<DemoBlock withThemeSwitcher path="patterns/server-datagrid-page/example" />
|
|
243
|
+
|
|
244
|
+
## Loading State
|
|
245
|
+
|
|
246
|
+
The initial loading state before the first fetch completes — DataGrid rendered with `loading={true}` and `rows={[]}`. The built-in loading overlay displays a skeleton while the server responds.
|
|
247
|
+
|
|
248
|
+
<DemoBlock withThemeSwitcher path="patterns/server-datagrid-page/with-loading" />
|
|
249
|
+
|
|
250
|
+
## Empty State
|
|
251
|
+
|
|
252
|
+
Server returns `{ rows: [], totalRows: 0 }` after filtering yields no results. The DataGrid shows a custom `NoRowsOverlay` with "No results match your filters" and a button to clear all filters.
|
|
253
|
+
|
|
254
|
+
<DemoBlock withThemeSwitcher path="patterns/server-datagrid-page/with-empty-state" />
|
|
255
|
+
|
|
256
|
+
## Error State
|
|
257
|
+
|
|
258
|
+
The fetch function rejects with an error. The component shows an error banner above the DataGrid with a retry button, keeping the page layout intact.
|
|
259
|
+
|
|
260
|
+
<DemoBlock withThemeSwitcher path="patterns/server-datagrid-page/with-error" />
|
|
261
|
+
|
|
262
|
+
## Implementation Checklist
|
|
263
|
+
|
|
264
|
+
1. **Define your row type** — Create a TypeScript `type Row` per the Data Contract section. Every row must have a unique `id` field.
|
|
265
|
+
2. **Create your fetch function** — Implement an async function matching the `FetchParams → FetchResult` contract. This is where you call your API or ElasticSearch. If using React Query, wrap it in a `useQuery` hook with `[page, pageSize, sortModel, filterModel, quickFilterText]` as the query key.
|
|
266
|
+
3. **Define column definitions** — Create a `GridColDef[]` array. Use RS custom column types (`rsString`, `rsNumber`, `rsSingleSelect`, `rsMultipleSelect`) for built-in filter operators. Add `renderCell` for rich content.
|
|
267
|
+
4. **Create the toolbar** — Build a `CustomToolbar` component using `GridToolbarContainer` with filter, columns, density, export, and quick search controls.
|
|
268
|
+
5. **Set up state** — Add all hooks from the State Management section: `rows`, `totalRows`, `loading`, `page`, `pageSize`, `sortModel`, `filterModel`, `selectionModel`, `quickFilterText`, and the debounce ref.
|
|
269
|
+
6. **Wire the fetch effect** — Use `useEffect` to call your fetch function whenever `page`, `pageSize`, `sortModel`, `filterModel`, or `quickFilterText` changes.
|
|
270
|
+
7. **Configure the DataGrid** — Pass `paginationMode="server"`, `sortingMode="server"`, `filterMode="server"`, `loading`, `rowCount={totalRows}`, and all controlled state handlers (`onPageChange`, `onPageSizeChange`, `onSortModelChange`, `onFilterModelChange`).
|
|
271
|
+
8. **Debounce filter changes** — Use a 300ms `setTimeout` in `onFilterModelChange` to avoid excessive requests during rapid typing. Reset to page 0 on filter change.
|
|
272
|
+
9. **Add bulk action bar** — Conditionally render when `selectionModel.length > 0`.
|
|
273
|
+
10. **Handle edge cases** — Add loading state (`loading` prop), empty state (custom `NoRowsOverlay`), and error state (conditional render with retry).
|
|
274
|
+
11. **Verify** — Test page navigation, sort toggling, filter operators, debounced quick search, loading overlay, empty results, and error recovery.
|
|
275
|
+
|
|
276
|
+
## Keyboard & Accessibility
|
|
277
|
+
|
|
278
|
+
The MUI DataGrid provides extensive built-in keyboard support:
|
|
279
|
+
|
|
280
|
+
- **Arrow keys** navigate between cells
|
|
281
|
+
- **Page Up / Page Down** move between pages
|
|
282
|
+
- **Home / End** jump to first/last cell in a row
|
|
283
|
+
- **Enter** activates cell editing or expands a row
|
|
284
|
+
- **Space** toggles checkbox selection on the focused row
|
|
285
|
+
- **Tab** moves focus between the toolbar and the grid
|
|
286
|
+
|
|
287
|
+
As an implementor, ensure:
|
|
288
|
+
|
|
289
|
+
- Every `IconButtonLink` in the actions column has an accessible `aria-label` (e.g. `aria-label="View details"`)
|
|
290
|
+
- Status `Pill` components use meaningful color contrast — don't rely on color alone
|
|
291
|
+
- Bulk action buttons have descriptive labels (e.g. "Delete 3 selected" instead of just "Delete")
|
|
292
|
+
- The loading state is announced to screen readers (`aria-busy="true"` on the container)
|
|
293
|
+
- Error messages are in a live region (`aria-live="polite"`) so screen readers announce them
|
|
294
|
+
- Custom `NoRowsOverlay` content is announced to screen readers
|
|
295
|
+
|
|
296
|
+
## Related Patterns
|
|
297
|
+
|
|
298
|
+
{/* prettier-ignore */}
|
|
299
|
+
- <Link href="/patterns/datagrid-page" as={RouterLink}>Datagrid Page</Link> — use when all data can be loaded upfront and filtering/sorting can happen client-side
|
|
300
|
+
- <Link href="/patterns/drilldown-datagrid-page" as={RouterLink}>Drilldown Datagrid Page</Link> — use when you want summary DataCards above the grid with one-way filter push
|
|
301
|
+
- <Link href="/patterns/crossfiltered-datagrid-page" as={RouterLink}>Cross-filtered Datagrid Page</Link> — use when DataCard counts should recalculate as filters change (two-way sync)
|