@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.
Files changed (258) hide show
  1. package/README.md +203 -0
  2. package/consumer-instructions/.cursorrules +80 -0
  3. package/consumer-instructions/.windsurfrules +80 -0
  4. package/consumer-instructions/CLAUDE.md +87 -0
  5. package/consumer-instructions/redsift-design-system.instructions.md +87 -0
  6. package/data/demos/patterns/crossfiltered-datagrid-page/example.tsx +750 -0
  7. package/data/demos/patterns/crossfiltered-datagrid-page/with-empty-state.tsx +111 -0
  8. package/data/demos/patterns/crossfiltered-datagrid-page/with-error.tsx +122 -0
  9. package/data/demos/patterns/crossfiltered-datagrid-page/with-loading.tsx +88 -0
  10. package/data/demos/patterns/datagrid-page/example.tsx +521 -0
  11. package/data/demos/patterns/datagrid-page/with-empty-state.tsx +80 -0
  12. package/data/demos/patterns/datagrid-page/with-error.tsx +96 -0
  13. package/data/demos/patterns/datagrid-page/with-loading.tsx +57 -0
  14. package/data/demos/patterns/drilldown-datagrid-page/example.tsx +715 -0
  15. package/data/demos/patterns/drilldown-datagrid-page/with-empty-state.tsx +113 -0
  16. package/data/demos/patterns/drilldown-datagrid-page/with-error.tsx +125 -0
  17. package/data/demos/patterns/drilldown-datagrid-page/with-loading.tsx +90 -0
  18. package/data/demos/patterns/server-datagrid-page/example.tsx +757 -0
  19. package/data/demos/patterns/server-datagrid-page/with-empty-state.tsx +107 -0
  20. package/data/demos/patterns/server-datagrid-page/with-error.tsx +107 -0
  21. package/data/demos/patterns/server-datagrid-page/with-loading.tsx +63 -0
  22. package/data/docs/components/charts/Arc.json +179 -0
  23. package/data/docs/components/charts/Arcs.json +104 -0
  24. package/data/docs/components/charts/Axis.json +269 -0
  25. package/data/docs/components/charts/Bar.json +236 -0
  26. package/data/docs/components/charts/BarChart.json +852 -0
  27. package/data/docs/components/charts/BarChartBars.json +18 -0
  28. package/data/docs/components/charts/BarChartGroupedTooltip.json +9 -0
  29. package/data/docs/components/charts/BarChartOverlay.json +34 -0
  30. package/data/docs/components/charts/BarChartSection.json +94 -0
  31. package/data/docs/components/charts/BaseBarChart.json +852 -0
  32. package/data/docs/components/charts/ChartContainerTitle.json +574 -0
  33. package/data/docs/components/charts/DataPoint.json +160 -0
  34. package/data/docs/components/charts/Dot.json +191 -0
  35. package/data/docs/components/charts/EmptyBarChart.json +852 -0
  36. package/data/docs/components/charts/EmptyLineChart.json +753 -0
  37. package/data/docs/components/charts/EmptyPieChart.json +826 -0
  38. package/data/docs/components/charts/EmptyScatterPlot.json +802 -0
  39. package/data/docs/components/charts/Legend.json +510 -0
  40. package/data/docs/components/charts/LegendItem.json +128 -0
  41. package/data/docs/components/charts/Line.json +69 -0
  42. package/data/docs/components/charts/LineChart.json +753 -0
  43. package/data/docs/components/charts/LoadingBarChart.json +852 -0
  44. package/data/docs/components/charts/LoadingLineChart.json +753 -0
  45. package/data/docs/components/charts/LoadingPieChart.json +826 -0
  46. package/data/docs/components/charts/LoadingScatterPlot.json +802 -0
  47. package/data/docs/components/charts/PieChart.json +826 -0
  48. package/data/docs/components/charts/RenderedLineChart.json +753 -0
  49. package/data/docs/components/charts/RenderedLinearBarChart.json +823 -0
  50. package/data/docs/components/charts/RenderedOrdinalBarChart.json +852 -0
  51. package/data/docs/components/charts/RenderedPieChart.json +826 -0
  52. package/data/docs/components/charts/RenderedScatterPlot.json +802 -0
  53. package/data/docs/components/charts/ScatterPlot.json +802 -0
  54. package/data/docs/components/charts/getAxisType.json +9 -0
  55. package/data/docs/components/charts/getComponentPosition.json +9 -0
  56. package/data/docs/components/dashboard/ChartEmptyState.json +42 -0
  57. package/data/docs/components/dashboard/Dashboard.json +26 -0
  58. package/data/docs/components/dashboard/DataCard.json +300 -0
  59. package/data/docs/components/dashboard/DataCardBody.json +431 -0
  60. package/data/docs/components/dashboard/DataCardHeader.json +304 -0
  61. package/data/docs/components/dashboard/DataCardListbox.json +529 -0
  62. package/data/docs/components/dashboard/DataRow.json +539 -0
  63. package/data/docs/components/dashboard/DrilldownItem.json +342 -0
  64. package/data/docs/components/dashboard/FilterableBarChart.json +83 -0
  65. package/data/docs/components/dashboard/FilterableDataGrid.json +83 -0
  66. package/data/docs/components/dashboard/FilterablePieChart.json +83 -0
  67. package/data/docs/components/dashboard/FilterableScatterPlot.json +83 -0
  68. package/data/docs/components/dashboard/PdfDocument.json +58 -0
  69. package/data/docs/components/dashboard/PdfExportButton.json +458 -0
  70. package/data/docs/components/dashboard/TimeSeriesBarChart.json +172 -0
  71. package/data/docs/components/dashboard/WithFilters.json +83 -0
  72. package/data/docs/components/design-system/ActiveDescendantListbox.json +521 -0
  73. package/data/docs/components/design-system/Alert.json +349 -0
  74. package/data/docs/components/design-system/AppBar.json +64 -0
  75. package/data/docs/components/design-system/AppContainer.json +67 -0
  76. package/data/docs/components/design-system/AppContent.json +566 -0
  77. package/data/docs/components/design-system/AppSidePanel.json +87 -0
  78. package/data/docs/components/design-system/Badge.json +293 -0
  79. package/data/docs/components/design-system/BaseBreadcrumbs.json +298 -0
  80. package/data/docs/components/design-system/BaseGrid.json +543 -0
  81. package/data/docs/components/design-system/BaseSkeleton.json +338 -0
  82. package/data/docs/components/design-system/BreadcrumbItem.json +231 -0
  83. package/data/docs/components/design-system/Breadcrumbs.json +298 -0
  84. package/data/docs/components/design-system/Button.json +402 -0
  85. package/data/docs/components/design-system/ButtonGroup.json +415 -0
  86. package/data/docs/components/design-system/ButtonLink.json +568 -0
  87. package/data/docs/components/design-system/Card.json +328 -0
  88. package/data/docs/components/design-system/CardActions.json +431 -0
  89. package/data/docs/components/design-system/CardBody.json +431 -0
  90. package/data/docs/components/design-system/CardHeader.json +428 -0
  91. package/data/docs/components/design-system/Checkbox.json +426 -0
  92. package/data/docs/components/design-system/CheckboxGroup.json +382 -0
  93. package/data/docs/components/design-system/ConditionalWrapper.json +40 -0
  94. package/data/docs/components/design-system/DetailedCard.json +401 -0
  95. package/data/docs/components/design-system/DetailedCardCollapsibleSectionItems.json +29 -0
  96. package/data/docs/components/design-system/DetailedCardHeader.json +37 -0
  97. package/data/docs/components/design-system/DetailedCardSection.json +90 -0
  98. package/data/docs/components/design-system/DetailedCardSectionItem.json +109 -0
  99. package/data/docs/components/design-system/Flexbox.json +523 -0
  100. package/data/docs/components/design-system/FocusWithinGroup.json +9 -0
  101. package/data/docs/components/design-system/Grid.json +543 -0
  102. package/data/docs/components/design-system/GridItem.json +388 -0
  103. package/data/docs/components/design-system/Heading.json +390 -0
  104. package/data/docs/components/design-system/Icon.json +325 -0
  105. package/data/docs/components/design-system/IconButton.json +371 -0
  106. package/data/docs/components/design-system/IconButtonLink.json +588 -0
  107. package/data/docs/components/design-system/Item.json +554 -0
  108. package/data/docs/components/design-system/Link.json +552 -0
  109. package/data/docs/components/design-system/LinkButton.json +397 -0
  110. package/data/docs/components/design-system/Listbox.json +529 -0
  111. package/data/docs/components/design-system/Number.json +773 -0
  112. package/data/docs/components/design-system/NumberField.json +594 -0
  113. package/data/docs/components/design-system/Pill.json +378 -0
  114. package/data/docs/components/design-system/ProgressBar.json +121 -0
  115. package/data/docs/components/design-system/RadarSvgLinearGradient.json +9 -0
  116. package/data/docs/components/design-system/Radio.json +415 -0
  117. package/data/docs/components/design-system/RadioGroup.json +382 -0
  118. package/data/docs/components/design-system/RenderedListboxItem.json +18 -0
  119. package/data/docs/components/design-system/RovingTabindexListbox.json +521 -0
  120. package/data/docs/components/design-system/Shield.json +360 -0
  121. package/data/docs/components/design-system/SideNavigationMenu.json +144 -0
  122. package/data/docs/components/design-system/SideNavigationMenuBar.json +89 -0
  123. package/data/docs/components/design-system/SideNavigationMenuItem.json +319 -0
  124. package/data/docs/components/design-system/Skeleton.json +338 -0
  125. package/data/docs/components/design-system/SkeletonCircle.json +338 -0
  126. package/data/docs/components/design-system/SkeletonText.json +371 -0
  127. package/data/docs/components/design-system/Spinner.json +291 -0
  128. package/data/docs/components/design-system/Switch.json +415 -0
  129. package/data/docs/components/design-system/SwitchGroup.json +382 -0
  130. package/data/docs/components/design-system/Text.json +418 -0
  131. package/data/docs/components/design-system/TextArea.json +501 -0
  132. package/data/docs/components/design-system/TextField.json +539 -0
  133. package/data/docs/components/design-system/sizeToDimension.json +9 -0
  134. package/data/docs/components/pickers/BaseCombobox.json +320 -0
  135. package/data/docs/components/pickers/BaseComboboxContent.json +453 -0
  136. package/data/docs/components/pickers/BaseMenuButton.json +240 -0
  137. package/data/docs/components/pickers/BaseMenuButtonContent.json +442 -0
  138. package/data/docs/components/pickers/BaseSelect.json +258 -0
  139. package/data/docs/components/pickers/Combobox.json +320 -0
  140. package/data/docs/components/pickers/ComboboxContent.json +453 -0
  141. package/data/docs/components/pickers/ComboboxContentFooter.json +431 -0
  142. package/data/docs/components/pickers/ComboboxContentHeader.json +431 -0
  143. package/data/docs/components/pickers/ComboboxContentListbox.json +541 -0
  144. package/data/docs/components/pickers/ComboboxTrigger.json +336 -0
  145. package/data/docs/components/pickers/Item.json +554 -0
  146. package/data/docs/components/pickers/MenuButton.json +240 -0
  147. package/data/docs/components/pickers/MenuButtonContent.json +442 -0
  148. package/data/docs/components/pickers/MenuButtonContentFooter.json +431 -0
  149. package/data/docs/components/pickers/MenuButtonContentHeader.json +431 -0
  150. package/data/docs/components/pickers/MenuButtonContentMenu.json +523 -0
  151. package/data/docs/components/pickers/MenuButtonTrigger.json +287 -0
  152. package/data/docs/components/pickers/Select.json +258 -0
  153. package/data/docs/components/pickers/SelectContent.json +442 -0
  154. package/data/docs/components/pickers/SelectTrigger.json +298 -0
  155. package/data/docs/components/popovers/BaseDialog.json +114 -0
  156. package/data/docs/components/popovers/BaseDialogContent.json +21 -0
  157. package/data/docs/components/popovers/BasePopover.json +171 -0
  158. package/data/docs/components/popovers/BaseToggletip.json +184 -0
  159. package/data/docs/components/popovers/BaseTooltip.json +121 -0
  160. package/data/docs/components/popovers/Button.json +402 -0
  161. package/data/docs/components/popovers/ButtonLink.json +568 -0
  162. package/data/docs/components/popovers/Dialog.json +114 -0
  163. package/data/docs/components/popovers/DialogContent.json +21 -0
  164. package/data/docs/components/popovers/DialogContentActions.json +442 -0
  165. package/data/docs/components/popovers/DialogContentBody.json +442 -0
  166. package/data/docs/components/popovers/DialogContentHeader.json +76 -0
  167. package/data/docs/components/popovers/DialogTrigger.json +276 -0
  168. package/data/docs/components/popovers/IconButton.json +371 -0
  169. package/data/docs/components/popovers/IconButtonLink.json +588 -0
  170. package/data/docs/components/popovers/Link.json +552 -0
  171. package/data/docs/components/popovers/LinkButton.json +397 -0
  172. package/data/docs/components/popovers/Popover.json +171 -0
  173. package/data/docs/components/popovers/PopoverContent.json +442 -0
  174. package/data/docs/components/popovers/PopoverTrigger.json +276 -0
  175. package/data/docs/components/popovers/Toast.json +145 -0
  176. package/data/docs/components/popovers/ToastContainer.json +122 -0
  177. package/data/docs/components/popovers/Toggletip.json +184 -0
  178. package/data/docs/components/popovers/ToggletipContent.json +402 -0
  179. package/data/docs/components/popovers/ToggletipTrigger.json +276 -0
  180. package/data/docs/components/popovers/Tooltip.json +121 -0
  181. package/data/docs/components/popovers/TooltipContent.json +402 -0
  182. package/data/docs/components/popovers/TooltipTrigger.json +276 -0
  183. package/data/docs/components/products/Dialog.json +106 -0
  184. package/data/docs/components/products/MenuButton.json +232 -0
  185. package/data/docs/components/products/PulsedRadarImage.json +9 -0
  186. package/data/docs/components/products/RadarButton.json +402 -0
  187. package/data/docs/components/products/RadarItem.json +554 -0
  188. package/data/docs/components/table/ControlledPagination.json +9 -0
  189. package/data/docs/components/table/DataGrid.json +93 -0
  190. package/data/docs/components/table/GridToolbarFilterSemanticField.json +69 -0
  191. package/data/docs/components/table/ServerSideControlledPagination.json +9 -0
  192. package/data/docs/components/table/StatefulDataGrid.json +117 -0
  193. package/data/docs/components/table/TextCell.json +118 -0
  194. package/data/docs/components/table/Toolbar.json +145 -0
  195. package/data/docs/components/table/ToolbarWrapper.json +9 -0
  196. package/data/docs/components-index.json +1206 -0
  197. package/data/docs/components.json +55694 -0
  198. package/data/docs/llms-full.txt +8012 -0
  199. package/data/docs/llms.txt +234 -0
  200. package/data/docs/patterns-catalog.md +359 -0
  201. package/data/docs/patterns.json +712 -0
  202. package/data/metadata.json +4 -0
  203. package/data/patterns/crossfiltered-datagrid-page.mdx +386 -0
  204. package/data/patterns/datagrid-page.mdx +214 -0
  205. package/data/patterns/drilldown-datagrid-page.mdx +291 -0
  206. package/data/patterns/server-datagrid-page.mdx +301 -0
  207. package/data/tokens/properties/components/dark-components.json +1108 -0
  208. package/data/tokens/properties/components/light-components.json +1108 -0
  209. package/data/tokens/properties/core/border-radius.json +3 -0
  210. package/data/tokens/properties/core/color.json +280 -0
  211. package/data/tokens/properties/core/layout.json +14 -0
  212. package/data/tokens/properties/core/typography.json +199 -0
  213. package/data/tokens/redsift-design-tokens.css +1391 -0
  214. package/dist/data-store.d.ts +47 -0
  215. package/dist/data-store.d.ts.map +1 -0
  216. package/dist/data-store.js +152 -0
  217. package/dist/data-store.js.map +1 -0
  218. package/dist/index.d.ts +16 -0
  219. package/dist/index.d.ts.map +1 -0
  220. package/dist/index.js +50 -0
  221. package/dist/index.js.map +1 -0
  222. package/dist/init.d.ts +14 -0
  223. package/dist/init.d.ts.map +1 -0
  224. package/dist/init.js +137 -0
  225. package/dist/init.js.map +1 -0
  226. package/dist/paths.d.ts +30 -0
  227. package/dist/paths.d.ts.map +1 -0
  228. package/dist/paths.js +53 -0
  229. package/dist/paths.js.map +1 -0
  230. package/dist/pattern-store.d.ts +41 -0
  231. package/dist/pattern-store.d.ts.map +1 -0
  232. package/dist/pattern-store.js +177 -0
  233. package/dist/pattern-store.js.map +1 -0
  234. package/dist/prompts.d.ts +14 -0
  235. package/dist/prompts.d.ts.map +1 -0
  236. package/dist/prompts.js +762 -0
  237. package/dist/prompts.js.map +1 -0
  238. package/dist/resources.d.ts +14 -0
  239. package/dist/resources.d.ts.map +1 -0
  240. package/dist/resources.js +482 -0
  241. package/dist/resources.js.map +1 -0
  242. package/dist/scaffold.d.ts +31 -0
  243. package/dist/scaffold.d.ts.map +1 -0
  244. package/dist/scaffold.js +239 -0
  245. package/dist/scaffold.js.map +1 -0
  246. package/dist/token-store.d.ts +70 -0
  247. package/dist/token-store.d.ts.map +1 -0
  248. package/dist/token-store.js +196 -0
  249. package/dist/token-store.js.map +1 -0
  250. package/dist/tools.d.ts +15 -0
  251. package/dist/tools.d.ts.map +1 -0
  252. package/dist/tools.js +491 -0
  253. package/dist/tools.js.map +1 -0
  254. package/dist/types.d.ts +108 -0
  255. package/dist/types.d.ts.map +1 -0
  256. package/dist/types.js +17 -0
  257. package/dist/types.js.map +1 -0
  258. package/package.json +39 -0
@@ -0,0 +1,4 @@
1
+ {
2
+ "generatedAt": "2026-03-27T16:15:06.727Z",
3
+ "commitSha": "2c2f13c866eb3a49a10b4f0943ff599324f851b2"
4
+ }
@@ -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)