@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,386 @@
|
|
|
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 Cross-filtered Datagrid Page extends the [Drilldown Datagrid Page](/patterns/drilldown-datagrid-page) with **two-way synchronization** between DataCards and the DataGrid filter panel. This means:
|
|
10
|
+
|
|
11
|
+
- Clicking a **DrilldownItem** adds an `isAnyOf` filter to the DataGrid — just like the one-way pattern
|
|
12
|
+
- Changing a filter in the **DataGrid filter panel** updates the DataCard selection state — the card highlights reflect the active filter
|
|
13
|
+
- DataCard **counts recalculate** against the cross-filtered dataset — filtering by category changes the status counts, and vice versa
|
|
14
|
+
|
|
15
|
+
Each DataCard's counts are computed by applying **all filters except the card's own field**. This prevents a card from filtering itself: selecting "active" in the Status card doesn't zero out the other status values — it only changes the Category and Active card counts.
|
|
16
|
+
|
|
17
|
+
When the user sets an `is` or `isNot` filter via the filter panel, the corresponding DataCard **clears** — all DrilldownItems are deselected. Clicking a DrilldownItem after that overwrites the incompatible filter with a fresh `isAnyOf` item.
|
|
18
|
+
|
|
19
|
+
## When to Use
|
|
20
|
+
|
|
21
|
+
- You want **dashboard-style KPI cards** that stay in sync with the table below
|
|
22
|
+
- DataCard counts should **recalculate** as filters change — reflecting the current filter context
|
|
23
|
+
- Clicking a card value should **add a filter** to the datagrid
|
|
24
|
+
- Changing the filter panel should **update the card selection** (bidirectional)
|
|
25
|
+
- The dataset is small enough to load entirely (under ~1,000 rows)
|
|
26
|
+
|
|
27
|
+
When **not** to use:
|
|
28
|
+
|
|
29
|
+
- DataCard counts should always show full-dataset totals — use [Drilldown Datagrid Page](/patterns/drilldown-datagrid-page) instead
|
|
30
|
+
- No summary cards are needed — use [Datagrid Page](/patterns/datagrid-page) instead
|
|
31
|
+
- The dataset is too large to load entirely — combine with [Server Datagrid Page](/patterns/server-datagrid-page) patterns
|
|
32
|
+
|
|
33
|
+
## Anatomy
|
|
34
|
+
|
|
35
|
+
1. **DataCard Row** — `Flexbox` with 1–N `DataCard` components. Counts are cross-filtered: each card excludes its own field from the filter calculation
|
|
36
|
+
2. **Toolbar** — `Flexbox` wrapping MUI's `GridToolbarContainer` with `GridToolbarFilterButton`, `GridToolbarColumnsButton`, `GridToolbarDensitySelector`, `GridToolbarExport`, and `GridToolbarQuickFilter`
|
|
37
|
+
3. **DataGrid** — the table itself with controlled `filterModel` + `onFilterModelChange`. Rows are pre-filtered client-side
|
|
38
|
+
4. **Bulk Action Bar** — a `Flexbox` row shown when `selectionModel.length > 0`, containing a `Pill` with the selection count and action `Button` components
|
|
39
|
+
|
|
40
|
+
## Features
|
|
41
|
+
|
|
42
|
+
<FeatureTable
|
|
43
|
+
features={[
|
|
44
|
+
{
|
|
45
|
+
feature: 'Cross-filtered aggregation',
|
|
46
|
+
required: true,
|
|
47
|
+
description: (
|
|
48
|
+
<>
|
|
49
|
+
DataCard counts recompute from rows filtered by all fields <strong>except</strong> the card's own field
|
|
50
|
+
</>
|
|
51
|
+
),
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
feature: 'Two-way filter sync',
|
|
55
|
+
required: true,
|
|
56
|
+
description: (
|
|
57
|
+
<>
|
|
58
|
+
DataCard selections derive from <code>filterModel</code>; DataCard clicks update <code>filterModel</code>
|
|
59
|
+
</>
|
|
60
|
+
),
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
feature: 'DrilldownItem filter push',
|
|
64
|
+
required: true,
|
|
65
|
+
description: (
|
|
66
|
+
<>
|
|
67
|
+
Clicking a <code>DrilldownItem</code> adds an <code>isAnyOf</code> filter to the DataGrid's{' '}
|
|
68
|
+
<code>filterModel</code>
|
|
69
|
+
</>
|
|
70
|
+
),
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
feature: 'Listbox multi-select',
|
|
74
|
+
required: true,
|
|
75
|
+
description: (
|
|
76
|
+
<>
|
|
77
|
+
<code>DataCard.Listbox</code> with <code>{'selectionMode="multiple"'}</code> and controlled{' '}
|
|
78
|
+
<code>values</code> for multi-value fields
|
|
79
|
+
</>
|
|
80
|
+
),
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
feature: (
|
|
84
|
+
<>
|
|
85
|
+
<code>is</code>/<code>isNot</code> clearing
|
|
86
|
+
</>
|
|
87
|
+
),
|
|
88
|
+
required: true,
|
|
89
|
+
description: (
|
|
90
|
+
<>
|
|
91
|
+
When filter panel has <code>is</code>/<code>isNot</code> for a card's field, the DataCard selection clears
|
|
92
|
+
</>
|
|
93
|
+
),
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
feature: 'Client-side row filtering',
|
|
97
|
+
required: true,
|
|
98
|
+
description: (
|
|
99
|
+
<>
|
|
100
|
+
<code>applyFilters</code> function filters the full row set by the current <code>filterModel</code>
|
|
101
|
+
</>
|
|
102
|
+
),
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
feature: 'Column definitions',
|
|
106
|
+
required: true,
|
|
107
|
+
description: (
|
|
108
|
+
<>
|
|
109
|
+
<code>GridColDef[]</code> with custom types: <code>rsString</code>, <code>rsNumber</code>,{' '}
|
|
110
|
+
<code>rsSingleSelect</code>, <code>rsMultipleSelect</code>
|
|
111
|
+
</>
|
|
112
|
+
),
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
feature: 'Toolbar',
|
|
116
|
+
required: true,
|
|
117
|
+
description: 'Built-in toolbar with filter, columns, density, export, and quick search controls',
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
feature: 'Custom cell renderers',
|
|
121
|
+
required: true,
|
|
122
|
+
description: (
|
|
123
|
+
<>
|
|
124
|
+
<code>renderCell</code> returning <code>TextCell</code>, <code>Pill</code>, <code>Icon</code>,{' '}
|
|
125
|
+
<code>IconButtonLink</code>
|
|
126
|
+
</>
|
|
127
|
+
),
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
feature: 'Boolean toggle',
|
|
131
|
+
required: false,
|
|
132
|
+
description: (
|
|
133
|
+
<>
|
|
134
|
+
<code>DataCard.Body</code> with <code>DrilldownItem</code> <code>onClick</code> for boolean fields
|
|
135
|
+
(active/inactive)
|
|
136
|
+
</>
|
|
137
|
+
),
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
feature: 'Checkbox selection',
|
|
141
|
+
required: false,
|
|
142
|
+
description: (
|
|
143
|
+
<>
|
|
144
|
+
<code>checkboxSelection</code> + <code>rowSelectionModel</code>
|
|
145
|
+
</>
|
|
146
|
+
),
|
|
147
|
+
},
|
|
148
|
+
{ feature: 'Bulk action bar', required: false, description: 'Contextual buttons shown when rows are selected' },
|
|
149
|
+
]}
|
|
150
|
+
/>
|
|
151
|
+
|
|
152
|
+
### Components
|
|
153
|
+
|
|
154
|
+
{/* prettier-ignore */}
|
|
155
|
+
- <Link href="/table/datagrid" as={RouterLink}>DataGrid</Link> — the table itself (from `@redsift/table`)
|
|
156
|
+
- <Link href="/dashboard/data-card" as={RouterLink}>DataCard</Link> — summary card container with colored border (from `@redsift/dashboard`)
|
|
157
|
+
- <Link href="/dashboard/data-card" as={RouterLink}>DataCard.Header</Link> — card header with icon and title
|
|
158
|
+
- <Link href="/dashboard/data-card" as={RouterLink}>DataCard.Body</Link> — static row container for DataRow or DrilldownItem
|
|
159
|
+
- <Link href="/dashboard/data-card" as={RouterLink}>DataCard.Listbox</Link> — multi-select container synced with DataGrid filters
|
|
160
|
+
- <Link href="/dashboard/drilldown-item" as={RouterLink}>DrilldownItem</Link> — numeric value with legend and filter icon (from `@redsift/dashboard`)
|
|
161
|
+
- <Link href="/layout/flexbox" as={RouterLink}>Flexbox</Link> — page and toolbar layout
|
|
162
|
+
- <Link href="/forms/button" as={RouterLink}>Button</Link> — toolbar and bulk action buttons
|
|
163
|
+
- <Link href="/forms/icon-button-link" as={RouterLink}>IconButtonLink</Link> — action column navigation links
|
|
164
|
+
- <Link href="/data-display/pill" as={RouterLink}>Pill</Link> — status badges in cells and count indicators
|
|
165
|
+
- <Link href="/typography/text" as={RouterLink}>Text</Link> — cell content and labels
|
|
166
|
+
- <Link href="/media-and-icons/icon" as={RouterLink}>Icon</Link> — toolbar icons and cell indicators
|
|
167
|
+
|
|
168
|
+
## State Management
|
|
169
|
+
|
|
170
|
+
<CodeBlock codeString={`import { useCallback, useMemo, useState } from 'react';
|
|
171
|
+
import { GridFilterModel, GridSelectionModel } from '@mui/x-data-grid-pro';
|
|
172
|
+
|
|
173
|
+
// Filter state — the single source of truth, shared between DataCards and DataGrid
|
|
174
|
+
const [filterModel, setFilterModel] = useState<GridFilterModel>({ items: [] });
|
|
175
|
+
|
|
176
|
+
// Selection state
|
|
177
|
+
const [selectionModel, setSelectionModel] = useState<GridSelectionModel>([]);
|
|
178
|
+
|
|
179
|
+
// Cross-filtered counts — each card excludes its own field from the filter
|
|
180
|
+
const statusCounts = useMemo(
|
|
181
|
+
() => computeCounts(applyFilters(allRows, filterModel, 'status')),
|
|
182
|
+
[filterModel],
|
|
183
|
+
);
|
|
184
|
+
const categoryCounts = useMemo(
|
|
185
|
+
() => computeCounts(applyFilters(allRows, filterModel, 'category')),
|
|
186
|
+
[filterModel],
|
|
187
|
+
);
|
|
188
|
+
const activeCounts = useMemo(
|
|
189
|
+
() => computeCounts(applyFilters(allRows, filterModel, 'active')),
|
|
190
|
+
[filterModel],
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
// Rows displayed in the DataGrid — all filters applied (no exclusion)
|
|
194
|
+
const filteredRows = useMemo(
|
|
195
|
+
() => applyFilters(allRows, filterModel),
|
|
196
|
+
[filterModel],
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
// Derive DataCard selections from filterModel (returns [] for is/isNot)
|
|
200
|
+
const statusSelection = getSelectedFromFilterModel(filterModel, 'status');
|
|
201
|
+
const categorySelection = getSelectedFromFilterModel(filterModel, 'category');
|
|
202
|
+
`} />
|
|
203
|
+
|
|
204
|
+
## Cross-filtering Logic
|
|
205
|
+
|
|
206
|
+
<CodeBlock codeString={`// Apply filterModel to rows client-side.
|
|
207
|
+
// When excludeField is provided, filters for that field are skipped —
|
|
208
|
+
// this is how each DataCard gets counts that exclude its own field.
|
|
209
|
+
function applyFilters(rows: Row[], filterModel: GridFilterModel, excludeField?: string): Row[] {
|
|
210
|
+
return rows.filter((row) => {
|
|
211
|
+
for (const item of filterModel.items) {
|
|
212
|
+
if (item.field === excludeField) continue;
|
|
213
|
+
if (!item.value || (Array.isArray(item.value) && item.value.length === 0)) continue;
|
|
214
|
+
|
|
215
|
+
const rowValue = row[item.field as keyof Row];
|
|
216
|
+
|
|
217
|
+
switch (item.operator) {
|
|
218
|
+
case 'isAnyOf': {
|
|
219
|
+
const values = Array.isArray(item.value) ? item.value : [item.value];
|
|
220
|
+
if (!values.includes(String(rowValue))) return false;
|
|
221
|
+
break;
|
|
222
|
+
}
|
|
223
|
+
case 'is': {
|
|
224
|
+
if (item.field === 'active') {
|
|
225
|
+
if (rowValue !== (item.value === 'true')) return false;
|
|
226
|
+
} else {
|
|
227
|
+
if (String(rowValue) !== String(item.value)) return false;
|
|
228
|
+
}
|
|
229
|
+
break;
|
|
230
|
+
}
|
|
231
|
+
case 'isNot': {
|
|
232
|
+
if (String(rowValue) === String(item.value)) return false;
|
|
233
|
+
break;
|
|
234
|
+
}
|
|
235
|
+
default:
|
|
236
|
+
break;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return true;
|
|
240
|
+
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Compute aggregate counts from a row subset
|
|
245
|
+
function computeCounts(rows: Row[]) {
|
|
246
|
+
const status: Record<string, number> = {};
|
|
247
|
+
const category: Record<string, number> = {};
|
|
248
|
+
let activeCount = 0;
|
|
249
|
+
let inactiveCount = 0;
|
|
250
|
+
|
|
251
|
+
for (const row of rows) {
|
|
252
|
+
status[row.status] = (status[row.status] || 0) + 1;
|
|
253
|
+
category[row.category] = (category[row.category] || 0) + 1;
|
|
254
|
+
if (row.active) activeCount++;
|
|
255
|
+
else inactiveCount++;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return { status, category, activeCount, inactiveCount };
|
|
259
|
+
}
|
|
260
|
+
`} />
|
|
261
|
+
|
|
262
|
+
## Filter Helpers
|
|
263
|
+
|
|
264
|
+
<CodeBlock codeString={`// Read selected values for a field — only syncs with isAnyOf.
|
|
265
|
+
// Returns [] for is/isNot operators (clears the DataCard).
|
|
266
|
+
function getSelectedFromFilterModel(filterModel: GridFilterModel, fieldName: string): string[] {
|
|
267
|
+
const item = filterModel.items.find((i) => i.field === fieldName);
|
|
268
|
+
if (!item || item.value === undefined) return [];
|
|
269
|
+
if (item.operator === 'isAnyOf') {
|
|
270
|
+
return Array.isArray(item.value) ? item.value : [item.value];
|
|
271
|
+
}
|
|
272
|
+
return []; // is, isNot, or other → clear card
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Write an isAnyOf filter — always creates a fresh item, never preserves
|
|
276
|
+
// an incompatible operator. Empty values removes the filter entirely.
|
|
277
|
+
function updateFilterModel(
|
|
278
|
+
filterModel: GridFilterModel,
|
|
279
|
+
fieldName: string,
|
|
280
|
+
values: string[],
|
|
281
|
+
): GridFilterModel {
|
|
282
|
+
const items = filterModel.items.filter((i) => i.field !== fieldName);
|
|
283
|
+
if (values.length > 0) {
|
|
284
|
+
items.push({
|
|
285
|
+
field: fieldName,
|
|
286
|
+
id: Math.floor(Math.random() \* 100000),
|
|
287
|
+
operator: 'isAnyOf',
|
|
288
|
+
value: values,
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
return { ...filterModel, items };
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Boolean filter helper — uses the 'is' operator with 'true'/'false'.
|
|
295
|
+
function updateBooleanFilter(
|
|
296
|
+
filterModel: GridFilterModel,
|
|
297
|
+
fieldName: string,
|
|
298
|
+
value: boolean | null,
|
|
299
|
+
): GridFilterModel {
|
|
300
|
+
const items = filterModel.items.filter((i) => i.field !== fieldName);
|
|
301
|
+
if (value !== null) {
|
|
302
|
+
items.push({
|
|
303
|
+
field: fieldName,
|
|
304
|
+
id: Math.floor(Math.random() \* 100000),
|
|
305
|
+
operator: 'is',
|
|
306
|
+
value: String(value),
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
return { ...filterModel, items };
|
|
310
|
+
}
|
|
311
|
+
`} />
|
|
312
|
+
|
|
313
|
+
## Data Contract
|
|
314
|
+
|
|
315
|
+
<CodeBlock codeString={`// Row type — same as Datagrid Page. Every row MUST have a unique 'id' field.
|
|
316
|
+
type Row = {
|
|
317
|
+
id: string;
|
|
318
|
+
label: string;
|
|
319
|
+
active: boolean;
|
|
320
|
+
status: 'active' | 'warning' | 'critical' | 'expired' | 'revoked';
|
|
321
|
+
category: 'primary' | 'secondary' | 'tertiary';
|
|
322
|
+
score: number;
|
|
323
|
+
expiry: string;
|
|
324
|
+
tags: string[];
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
// Aggregate counts — recomputed from cross-filtered rows
|
|
328
|
+
type AggregateCounts = {
|
|
329
|
+
status: Record<string, number>;
|
|
330
|
+
category: Record<string, number>;
|
|
331
|
+
activeCount: number;
|
|
332
|
+
inactiveCount: number;
|
|
333
|
+
};
|
|
334
|
+
`} />
|
|
335
|
+
|
|
336
|
+
## Example
|
|
337
|
+
|
|
338
|
+
The same 27-row dataset, with three cross-filtered DataCards. **Status card** counts are computed with status filters excluded (but category + active filters applied). **Category card** counts exclude category filters. **Active card** counts exclude active filters. This prevents a card from zeroing out its own non-selected values. Selecting "active" in the Status card changes the Category and Active counts, but all five status values still show their cross-filtered totals.
|
|
339
|
+
|
|
340
|
+
<DemoBlock withThemeSwitcher path="patterns/crossfiltered-datagrid-page/example" />
|
|
341
|
+
|
|
342
|
+
## Loading State
|
|
343
|
+
|
|
344
|
+
DataCards with `isLoading={true}` alongside a DataGrid with `loading={true}` and `rows={[]}`. Use this state while fetching initial data.
|
|
345
|
+
|
|
346
|
+
<DemoBlock withThemeSwitcher path="patterns/crossfiltered-datagrid-page/with-loading" />
|
|
347
|
+
|
|
348
|
+
## Empty State
|
|
349
|
+
|
|
350
|
+
DataCards showing zero counts on all DrilldownItems, with the DataGrid displaying a custom `NoRowsOverlay` indicating no results match the current filters.
|
|
351
|
+
|
|
352
|
+
<DemoBlock withThemeSwitcher path="patterns/crossfiltered-datagrid-page/with-empty-state" />
|
|
353
|
+
|
|
354
|
+
## Error State
|
|
355
|
+
|
|
356
|
+
An error banner above the DataCards and DataGrid with a retry button. DataCards switch to loading state during retry. The page layout remains intact.
|
|
357
|
+
|
|
358
|
+
<DemoBlock withThemeSwitcher path="patterns/crossfiltered-datagrid-page/with-error" />
|
|
359
|
+
|
|
360
|
+
## Implementation Checklist
|
|
361
|
+
|
|
362
|
+
1. **Define your row type** — Create a TypeScript `type Row` per the Data Contract section. Every row must have a unique `id` field.
|
|
363
|
+
2. **Write the `applyFilters` function** — Accepts `(rows, filterModel, excludeField?)`. Supports `isAnyOf`, `is`, `isNot` operators. When `excludeField` is provided, skips filters for that field.
|
|
364
|
+
3. **Write `computeCounts`** — Accepts a row array and returns aggregate counts per field value.
|
|
365
|
+
4. **Write filter helpers** — Implement `getSelectedFromFilterModel` (reads `isAnyOf` values, returns `[]` for `is`/`isNot`) and `updateFilterModel` (always writes `isAnyOf`, removes when empty).
|
|
366
|
+
5. **Compute cross-filtered counts** — For each DataCard, call `computeCounts(applyFilters(allRows, filterModel, cardField))`. This gives counts that exclude the card's own field.
|
|
367
|
+
6. **Compute filtered rows for DataGrid** — Call `applyFilters(allRows, filterModel)` without excluding any field. Pass the result as the DataGrid's `rows` prop.
|
|
368
|
+
7. **Build DataCards** — Create a `DataCard` for each field with `DataCard.Listbox` (multi-select with controlled `values` derived from `getSelectedFromFilterModel`) or `DataCard.Body` (boolean toggle). Wire `onChange` / `onClick` to `updateFilterModel` / `updateBooleanFilter`.
|
|
369
|
+
8. **Render the DataGrid** — Pass `rows={filteredRows}`, `filterModel`, and `onFilterModelChange={setFilterModel}` so the filter panel stays synced.
|
|
370
|
+
9. **Handle edge cases** — Add loading (DataCards `isLoading` + DataGrid `loading`), empty (zero counts + `NoRowsOverlay`), and error states (banner + retry).
|
|
371
|
+
|
|
372
|
+
## Keyboard & Accessibility
|
|
373
|
+
|
|
374
|
+
- **DrilldownItem** renders as a `<button>` — focusable via **Tab**, activates with **Enter** or **Space**
|
|
375
|
+
- **DataCard.Listbox** supports **Arrow Up/Down** to navigate between items and **Space** to toggle selection
|
|
376
|
+
- When a DrilldownItem is selected, it receives `aria-selected="true"` from the Listbox
|
|
377
|
+
- DataGrid keyboard navigation works unchanged (arrows, Page Up/Down, Space for checkboxes)
|
|
378
|
+
- Error banners use `role="alert"` and `aria-live="polite"` for screen reader announcement
|
|
379
|
+
- As counts update in real-time, screen readers should be able to query the updated values
|
|
380
|
+
|
|
381
|
+
## Related Patterns
|
|
382
|
+
|
|
383
|
+
{/* prettier-ignore */}
|
|
384
|
+
- <Link href="/patterns/drilldown-datagrid-page" as={RouterLink}>Drilldown Datagrid Page</Link> — use when DataCard counts should always show full-dataset totals (one-way)
|
|
385
|
+
- <Link href="/patterns/datagrid-page" as={RouterLink}>Datagrid Page</Link> — use when no summary cards are needed
|
|
386
|
+
- <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,214 @@
|
|
|
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 Datagrid Page is the most common layout across Red Sift products. It combines a full MUI DataGrid with a composable toolbar that includes columns, filters, density, export, reset, saved views, and quick search controls. This is the go-to pattern whenever you need to display a filterable, sortable, paginated collection of records — domains, certificates, email sources, or lookalikes.
|
|
10
|
+
|
|
11
|
+
Optionally, the page can include bulk selection with contextual action buttons, Saved Views (full CRUD for named table state presets), and Quick Views (a dropdown to switch column visibility presets). All filtering, sorting, and pagination in this pattern happen **client-side** — all rows are loaded into the DataGrid at once. For server-side data handling, see the [Server Datagrid Page](/patterns/server-datagrid-page) pattern.
|
|
12
|
+
|
|
13
|
+
## When to Use
|
|
14
|
+
|
|
15
|
+
- You need a **filterable, sortable, paginated table** of records
|
|
16
|
+
- The full dataset can be loaded upfront (typically under ~1,000 rows)
|
|
17
|
+
- All filtering, sorting, and pagination can happen **client-side** in the browser
|
|
18
|
+
- You want built-in column visibility, density, export, and quick search with zero backend work
|
|
19
|
+
- You need optional **bulk selection** with contextual action buttons
|
|
20
|
+
|
|
21
|
+
When **not** to use:
|
|
22
|
+
|
|
23
|
+
- The dataset has thousands or millions of rows — use [Server Datagrid Page](/patterns/server-datagrid-page) instead
|
|
24
|
+
- Sorting or filtering logic must live on the backend (e.g. ElasticSearch, SQL) — use [Server Datagrid Page](/patterns/server-datagrid-page) instead
|
|
25
|
+
|
|
26
|
+
## Anatomy
|
|
27
|
+
|
|
28
|
+
1. **Toolbar** — `Flexbox` wrapping MUI's `GridToolbarContainer` with `GridToolbarFilterButton`, `GridToolbarColumnsButton`, `GridToolbarDensitySelector`, `GridToolbarExport`, and `GridToolbarQuickFilter`
|
|
29
|
+
2. **DataGrid** — the table itself from `@redsift/table`, configured with `autoHeight`, `pagination`, `checkboxSelection`, and `GridColDef[]` columns
|
|
30
|
+
3. **Bulk Action Bar** — a `Flexbox` row shown when `selectionModel.length > 0`, containing a `Pill` with the selection count and action `Button` components
|
|
31
|
+
|
|
32
|
+
## Features
|
|
33
|
+
|
|
34
|
+
<FeatureTable
|
|
35
|
+
features={[
|
|
36
|
+
{
|
|
37
|
+
feature: 'Column definitions',
|
|
38
|
+
required: true,
|
|
39
|
+
description: (
|
|
40
|
+
<>
|
|
41
|
+
<code>GridColDef[]</code> array with custom types: <code>rsString</code>, <code>rsNumber</code>,{' '}
|
|
42
|
+
<code>rsSingleSelect</code>, <code>rsMultipleSelect</code>, <code>boolean</code>, <code>date</code>
|
|
43
|
+
</>
|
|
44
|
+
),
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
feature: 'Toolbar',
|
|
48
|
+
required: true,
|
|
49
|
+
description: 'Built-in toolbar with filter, columns, density, export, and quick search controls',
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
feature: 'Pagination',
|
|
53
|
+
required: true,
|
|
54
|
+
description: (
|
|
55
|
+
<>
|
|
56
|
+
Client-side pagination with configurable page size (<code>rowsPerPageOptions</code>)
|
|
57
|
+
</>
|
|
58
|
+
),
|
|
59
|
+
},
|
|
60
|
+
{ feature: 'Sorting', required: true, description: 'Client-side column sorting (built into DataGrid)' },
|
|
61
|
+
{ feature: 'Filtering', required: true, description: 'Client-side column filtering (built into DataGrid)' },
|
|
62
|
+
{
|
|
63
|
+
feature: 'Custom cell renderers',
|
|
64
|
+
required: true,
|
|
65
|
+
description: (
|
|
66
|
+
<>
|
|
67
|
+
<code>renderCell</code> returning <code>TextCell</code>, <code>Pill</code>, <code>Icon</code>,{' '}
|
|
68
|
+
<code>IconButtonLink</code> for rich cell content
|
|
69
|
+
</>
|
|
70
|
+
),
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
feature: 'Checkbox selection',
|
|
74
|
+
required: false,
|
|
75
|
+
description: (
|
|
76
|
+
<>
|
|
77
|
+
<code>checkboxSelection</code> + <code>rowSelectionModel</code> + <code>onRowSelectionModelChange</code> for
|
|
78
|
+
row selection
|
|
79
|
+
</>
|
|
80
|
+
),
|
|
81
|
+
},
|
|
82
|
+
{ feature: 'Bulk action bar', required: false, description: 'Contextual buttons shown when rows are selected' },
|
|
83
|
+
{
|
|
84
|
+
feature: 'Actions column',
|
|
85
|
+
required: false,
|
|
86
|
+
description: (
|
|
87
|
+
<>
|
|
88
|
+
Column with <code>IconButtonLink</code> for row-level navigation
|
|
89
|
+
</>
|
|
90
|
+
),
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
feature: 'Quick search',
|
|
94
|
+
required: false,
|
|
95
|
+
description: (
|
|
96
|
+
<>
|
|
97
|
+
<code>GridToolbarQuickFilter</code> for text search across all columns
|
|
98
|
+
</>
|
|
99
|
+
),
|
|
100
|
+
},
|
|
101
|
+
]}
|
|
102
|
+
/>
|
|
103
|
+
|
|
104
|
+
### Components
|
|
105
|
+
|
|
106
|
+
{/* prettier-ignore */}
|
|
107
|
+
- <Link href="/table/datagrid" as={RouterLink}>DataGrid</Link> — the table itself (from `@redsift/table`)
|
|
108
|
+
- <Link href="/layout/flexbox" as={RouterLink}>Flexbox</Link> — page and toolbar layout
|
|
109
|
+
- <Link href="/forms/button" as={RouterLink}>Button</Link> — toolbar and bulk action buttons
|
|
110
|
+
- <Link href="/forms/icon-button" as={RouterLink}>IconButton</Link> — toolbar icon controls (columns, filters, density, export, reset, save)
|
|
111
|
+
- <Link href="/forms/icon-button-link" as={RouterLink}>IconButtonLink</Link> — action column navigation links
|
|
112
|
+
- <Link href="/data-display/pill" as={RouterLink}>Pill</Link> — status badges in cells and count indicators
|
|
113
|
+
- <Link href="/typography/text" as={RouterLink}>Text</Link> — cell content, labels, and search placeholder
|
|
114
|
+
- <Link href="/media-and-icons/icon" as={RouterLink}>Icon</Link> — toolbar icons and cell indicators
|
|
115
|
+
- <Link href="/data-display/badge" as={RouterLink}>Badge</Link> — active filter count on the filter button
|
|
116
|
+
|
|
117
|
+
## State Management
|
|
118
|
+
|
|
119
|
+
<CodeBlock codeString={`import { useState } from 'react';
|
|
120
|
+
import { GridSelectionModel } from '@mui/x-data-grid-pro';
|
|
121
|
+
|
|
122
|
+
// Selection state — tracks which rows are checked
|
|
123
|
+
const [selectionModel, setSelectionModel] = useState<GridSelectionModel>([]);
|
|
124
|
+
|
|
125
|
+
// Page size — controlled if you need to persist it
|
|
126
|
+
// (DataGrid manages page/pageSize internally for client-side mode)
|
|
127
|
+
`} />
|
|
128
|
+
|
|
129
|
+
## Data Contract
|
|
130
|
+
|
|
131
|
+
<CodeBlock codeString={`// Row type — adapt fields to your domain
|
|
132
|
+
// Every row MUST have a unique 'id' field (string or number)
|
|
133
|
+
type Row = {
|
|
134
|
+
id: string;
|
|
135
|
+
label: string;
|
|
136
|
+
active: boolean;
|
|
137
|
+
status: 'active' | 'warning' | 'critical' | 'expired';
|
|
138
|
+
category: string;
|
|
139
|
+
score: number;
|
|
140
|
+
expiry: string; // ISO 8601 date string
|
|
141
|
+
tags: string[];
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
// Column definitions — use RS custom column types for built-in filter operators
|
|
145
|
+
import { GridColDef } from '@mui/x-data-grid-pro';
|
|
146
|
+
|
|
147
|
+
const columns: GridColDef[] = [
|
|
148
|
+
{ field: 'label', headerName: 'Domain', type: 'rsString', flex: 1 },
|
|
149
|
+
{ field: 'status', headerName: 'Status', type: 'rsSingleSelect', valueOptions: [...] },
|
|
150
|
+
{ field: 'score', headerName: 'Score', type: 'rsNumber', width: 100 },
|
|
151
|
+
{ field: 'expiry', headerName: 'Expiry', type: 'date', width: 140 },
|
|
152
|
+
{ field: 'tags', headerName: 'Tags', type: 'rsMultipleSelect', valueOptions: [...] },
|
|
153
|
+
];
|
|
154
|
+
`} />
|
|
155
|
+
|
|
156
|
+
## Example
|
|
157
|
+
|
|
158
|
+
A complete datagrid page showcasing all common column types: string (`rsString`), number (`rsNumber`), boolean, date, single-select with status pills (`rsSingleSelect`), single-select with icons (`rsSingleSelect`), and multi-select tags (`rsMultipleSelect`). Includes checkbox selection with a bulk action bar, an actions column with navigation links, and pagination set to 10/25/50 rows per page with 27 rows to exercise all breakpoints.
|
|
159
|
+
|
|
160
|
+
<DemoBlock withThemeSwitcher path="patterns/datagrid-page/example" />
|
|
161
|
+
|
|
162
|
+
## Loading State
|
|
163
|
+
|
|
164
|
+
DataGrid with `loading={true}` showing the built-in loading overlay. Use this state while fetching initial data or refreshing the dataset.
|
|
165
|
+
|
|
166
|
+
<DemoBlock withThemeSwitcher path="patterns/datagrid-page/with-loading" />
|
|
167
|
+
|
|
168
|
+
## Empty State
|
|
169
|
+
|
|
170
|
+
DataGrid with `rows={[]}` and a custom `NoRowsOverlay` slot displaying an empty illustration with a descriptive message. Use this when the dataset has no records or a filter returns no results.
|
|
171
|
+
|
|
172
|
+
<DemoBlock withThemeSwitcher path="patterns/datagrid-page/with-empty-state" />
|
|
173
|
+
|
|
174
|
+
## Error State
|
|
175
|
+
|
|
176
|
+
When data fetching fails, replace the DataGrid area with an error message and a retry button. This keeps the page layout intact while communicating the failure.
|
|
177
|
+
|
|
178
|
+
<DemoBlock withThemeSwitcher path="patterns/datagrid-page/with-error" />
|
|
179
|
+
|
|
180
|
+
## Implementation Checklist
|
|
181
|
+
|
|
182
|
+
1. **Define your row type** — Create a TypeScript `type Row` per the Data Contract section. Every row must have a unique `id` field.
|
|
183
|
+
2. **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 (pills, icons, links).
|
|
184
|
+
3. **Create the toolbar** — Build a `CustomToolbar` component using `GridToolbarContainer` with `GridToolbarFilterButton`, `GridToolbarColumnsButton`, `GridToolbarDensitySelector`, `GridToolbarExport`, and optionally `GridToolbarQuickFilter`.
|
|
185
|
+
4. **Set up state** — Add `useState<GridSelectionModel>([])` for checkbox selection (if needed).
|
|
186
|
+
5. **Render the DataGrid** — Pass `rows`, `columns`, `autoHeight`, `pagination`, `pageSize`, `rowsPerPageOptions`, `checkboxSelection`, `components={{ Toolbar: CustomToolbar }}`.
|
|
187
|
+
6. **Add bulk action bar** — Conditionally render a `Flexbox` with a `Pill` (selection count) and action `Button` components when `selectionModel.length > 0`.
|
|
188
|
+
7. **Handle edge cases** — Add loading state (`loading` prop), empty state (custom `NoRowsOverlay`), and error state (conditional render with retry).
|
|
189
|
+
8. **Verify** — Test pagination breakpoints, filter operators, column visibility toggle, density switching, and export.
|
|
190
|
+
|
|
191
|
+
## Keyboard & Accessibility
|
|
192
|
+
|
|
193
|
+
The MUI DataGrid provides extensive built-in keyboard support:
|
|
194
|
+
|
|
195
|
+
- **Arrow keys** navigate between cells
|
|
196
|
+
- **Page Up / Page Down** move between pages
|
|
197
|
+
- **Home / End** jump to first/last cell in a row
|
|
198
|
+
- **Enter** activates cell editing or expands a row
|
|
199
|
+
- **Space** toggles checkbox selection on the focused row
|
|
200
|
+
- **Tab** moves focus between the toolbar and the grid
|
|
201
|
+
|
|
202
|
+
As an implementor, ensure:
|
|
203
|
+
|
|
204
|
+
- Every `IconButtonLink` in the actions column has an accessible `aria-label` (e.g. `aria-label="View details"`)
|
|
205
|
+
- Status `Pill` components use meaningful color contrast — don't rely on color alone
|
|
206
|
+
- Bulk action buttons have descriptive labels (e.g. "Delete 3 selected" instead of just "Delete")
|
|
207
|
+
- Custom `NoRowsOverlay` content is announced to screen readers
|
|
208
|
+
|
|
209
|
+
## Related Patterns
|
|
210
|
+
|
|
211
|
+
{/* prettier-ignore */}
|
|
212
|
+
- <Link href="/patterns/server-datagrid-page" as={RouterLink}>Server Datagrid Page</Link> — use when the dataset is too large to load entirely, or when filtering/sorting must happen server-side
|
|
213
|
+
- <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
|
|
214
|
+
- <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)
|