@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,757 @@
1
+ import React, { useCallback, useEffect, useRef, useState } from 'react';
2
+ import { DataGrid, TextCell } from '@redsift/table';
3
+ import { Button, Flexbox, Icon, IconButtonLink, Pill } from '@redsift/design-system';
4
+ import { mdiArrowRight, mdiCheck, mdiClose, mdiShieldCheck, mdiLinkVariant, mdiLeaf } from '@redsift/icons';
5
+ import {
6
+ GridColDef,
7
+ GridFilterModel,
8
+ GridRenderCellParams,
9
+ GridRowSelectionModel,
10
+ GridSortModel,
11
+ } from '@mui/x-data-grid-pro';
12
+ import {
13
+ GridToolbarContainer,
14
+ GridToolbarColumnsButton,
15
+ GridToolbarDensitySelector,
16
+ GridToolbarFilterButton,
17
+ GridToolbarExport,
18
+ GridToolbarQuickFilter,
19
+ } from '@mui/x-data-grid-pro';
20
+
21
+ // -- Toolbar ----------------------------------------------------------------
22
+
23
+ const CustomToolbar = () => (
24
+ <GridToolbarContainer>
25
+ <Flexbox gap="0">
26
+ <GridToolbarFilterButton />
27
+ <GridToolbarColumnsButton />
28
+ <GridToolbarDensitySelector />
29
+ <GridToolbarExport />
30
+ <Flexbox marginLeft="8px">
31
+ <GridToolbarQuickFilter />
32
+ </Flexbox>
33
+ </Flexbox>
34
+ </GridToolbarContainer>
35
+ );
36
+
37
+ // -- Tag options for the rsMultipleSelect column ----------------------------
38
+
39
+ const TAG_OPTIONS = [
40
+ { value: 'production', label: 'Production' },
41
+ { value: 'staging', label: 'Staging' },
42
+ { value: 'legacy', label: 'Legacy' },
43
+ { value: 'infrastructure', label: 'Infrastructure' },
44
+ { value: 'cdn', label: 'CDN' },
45
+ { value: 'api', label: 'API' },
46
+ { value: 'auth', label: 'Auth' },
47
+ { value: 'ca', label: 'CA' },
48
+ { value: 'email', label: 'Email' },
49
+ { value: 'ecommerce', label: 'Ecommerce' },
50
+ { value: 'wildcard', label: 'Wildcard' },
51
+ { value: 'monitoring', label: 'Monitoring' },
52
+ { value: 'vpn', label: 'VPN' },
53
+ { value: 'billing', label: 'Billing' },
54
+ { value: 'ci', label: 'CI' },
55
+ { value: 'cms', label: 'CMS' },
56
+ ];
57
+
58
+ // -- Full dataset (lives "on the server") -----------------------------------
59
+
60
+ type Row = {
61
+ id: string;
62
+ label: string;
63
+ active: boolean;
64
+ status: string;
65
+ category: string;
66
+ score: number;
67
+ expiry: string;
68
+ tags: string[];
69
+ };
70
+
71
+ const DATABASE: Row[] = [
72
+ {
73
+ id: 'a3f8c1d2',
74
+ label: 'cdn.acme.com',
75
+ active: true,
76
+ status: 'active',
77
+ category: 'primary',
78
+ score: 92,
79
+ expiry: '2027-06-15T00:00:00Z',
80
+ tags: ['production', 'cdn'],
81
+ },
82
+ {
83
+ id: 'b7e2d4f6',
84
+ label: 'api.globex.io',
85
+ active: true,
86
+ status: 'warning',
87
+ category: 'primary',
88
+ score: 74,
89
+ expiry: '2026-05-01T12:00:00Z',
90
+ tags: ['production', 'api'],
91
+ },
92
+ {
93
+ id: 'c1a9b5e3',
94
+ label: '*.staging.acme.com',
95
+ active: true,
96
+ status: 'critical',
97
+ category: 'secondary',
98
+ score: 45,
99
+ expiry: '2026-03-28T23:59:59Z',
100
+ tags: ['staging'],
101
+ },
102
+ {
103
+ id: 'd4f0c8a7',
104
+ label: 'mail.initech.dev',
105
+ active: false,
106
+ status: 'expired',
107
+ category: 'tertiary',
108
+ score: 12,
109
+ expiry: '2025-11-30T08:45:00Z',
110
+ tags: ['legacy', 'email'],
111
+ },
112
+ {
113
+ id: 'e6b3d1f9',
114
+ label: 'Acme Root CA',
115
+ active: true,
116
+ status: 'active',
117
+ category: 'primary',
118
+ score: 100,
119
+ expiry: '2035-06-01T00:00:00Z',
120
+ tags: ['infrastructure'],
121
+ },
122
+ {
123
+ id: 'f2c7a5e8',
124
+ label: 'Issuing CA G2',
125
+ active: true,
126
+ status: 'active',
127
+ category: 'secondary',
128
+ score: 98,
129
+ expiry: '2031-12-31T23:59:59Z',
130
+ tags: ['infrastructure', 'ca'],
131
+ },
132
+ {
133
+ id: 'a8d4f6b1',
134
+ label: 'auth.globex.io',
135
+ active: false,
136
+ status: 'revoked',
137
+ category: 'primary',
138
+ score: 0,
139
+ expiry: '2026-09-10T17:30:00Z',
140
+ tags: ['production', 'auth'],
141
+ },
142
+ {
143
+ id: 'b5e9c3a2',
144
+ label: 'shop.initech.dev',
145
+ active: true,
146
+ status: 'warning',
147
+ category: 'tertiary',
148
+ score: 63,
149
+ expiry: '2026-04-20T00:00:00Z',
150
+ tags: ['ecommerce'],
151
+ },
152
+ {
153
+ id: 'c0f7d2b4',
154
+ label: 'Sectigo DV CA',
155
+ active: true,
156
+ status: 'active',
157
+ category: 'secondary',
158
+ score: 95,
159
+ expiry: '2030-01-01T00:00:00Z',
160
+ tags: ['infrastructure', 'ca'],
161
+ },
162
+ {
163
+ id: 'd9a1e5c6',
164
+ label: 'vault.initech.dev',
165
+ active: false,
166
+ status: 'expired',
167
+ category: 'primary',
168
+ score: 8,
169
+ expiry: '2024-07-22T14:00:00Z',
170
+ tags: ['legacy'],
171
+ },
172
+ {
173
+ id: 'e3b8f0d7',
174
+ label: '*.acme.com',
175
+ active: true,
176
+ status: 'active',
177
+ category: 'primary',
178
+ score: 88,
179
+ expiry: '2027-08-05T12:00:00Z',
180
+ tags: ['production', 'wildcard'],
181
+ },
182
+ {
183
+ id: 'f6c2a4e1',
184
+ label: 'portal.globex.io',
185
+ active: true,
186
+ status: 'critical',
187
+ category: 'tertiary',
188
+ score: 51,
189
+ expiry: '2026-03-26T10:00:00Z',
190
+ tags: [],
191
+ },
192
+ {
193
+ id: 'a1d7f3b9',
194
+ label: 'logs.initech.dev',
195
+ active: true,
196
+ status: 'active',
197
+ category: 'secondary',
198
+ score: 81,
199
+ expiry: '2028-02-14T00:00:00Z',
200
+ tags: ['production', 'monitoring'],
201
+ },
202
+ {
203
+ id: 'b4e8c6a3',
204
+ label: 'cms.acme.com',
205
+ active: false,
206
+ status: 'expired',
207
+ category: 'tertiary',
208
+ score: 5,
209
+ expiry: '2025-06-10T00:00:00Z',
210
+ tags: ['legacy', 'cms'],
211
+ },
212
+ {
213
+ id: 'c7f2d0b5',
214
+ label: 'DigiCert EV CA',
215
+ active: true,
216
+ status: 'active',
217
+ category: 'secondary',
218
+ score: 99,
219
+ expiry: '2033-09-15T00:00:00Z',
220
+ tags: ['infrastructure', 'ca'],
221
+ },
222
+ {
223
+ id: 'd0a3e9f1',
224
+ label: 'vpn.globex.io',
225
+ active: true,
226
+ status: 'warning',
227
+ category: 'primary',
228
+ score: 68,
229
+ expiry: '2026-04-12T00:00:00Z',
230
+ tags: ['production', 'vpn'],
231
+ },
232
+ {
233
+ id: 'e5b1c4d8',
234
+ label: '*.initech.dev',
235
+ active: true,
236
+ status: 'active',
237
+ category: 'primary',
238
+ score: 85,
239
+ expiry: '2027-11-20T00:00:00Z',
240
+ tags: ['production', 'wildcard'],
241
+ },
242
+ {
243
+ id: 'f8c6a2e7',
244
+ label: 'docs.acme.com',
245
+ active: true,
246
+ status: 'active',
247
+ category: 'tertiary',
248
+ score: 90,
249
+ expiry: '2028-07-01T00:00:00Z',
250
+ tags: ['production'],
251
+ },
252
+ {
253
+ id: 'a2d5f9b0',
254
+ label: 'billing.globex.io',
255
+ active: false,
256
+ status: 'revoked',
257
+ category: 'primary',
258
+ score: 0,
259
+ expiry: '2026-01-15T00:00:00Z',
260
+ tags: ['production', 'billing'],
261
+ },
262
+ {
263
+ id: 'b6e0c3a1',
264
+ label: 'staging.initech.dev',
265
+ active: true,
266
+ status: 'critical',
267
+ category: 'secondary',
268
+ score: 42,
269
+ expiry: '2026-03-27T00:00:00Z',
270
+ tags: ['staging', 'ci'],
271
+ },
272
+ {
273
+ id: 'c9f4d7b2',
274
+ label: "Let's Encrypt R3",
275
+ active: true,
276
+ status: 'active',
277
+ category: 'secondary',
278
+ score: 97,
279
+ expiry: '2030-09-30T00:00:00Z',
280
+ tags: ['infrastructure', 'ca'],
281
+ },
282
+ {
283
+ id: 'd3a8e1f5',
284
+ label: 'analytics.acme.com',
285
+ active: true,
286
+ status: 'warning',
287
+ category: 'tertiary',
288
+ score: 59,
289
+ expiry: '2026-04-05T00:00:00Z',
290
+ tags: ['production', 'monitoring'],
291
+ },
292
+ {
293
+ id: 'e7b2c5d9',
294
+ label: 'sso.globex.io',
295
+ active: true,
296
+ status: 'active',
297
+ category: 'primary',
298
+ score: 91,
299
+ expiry: '2027-12-01T00:00:00Z',
300
+ tags: ['production', 'auth'],
301
+ },
302
+ {
303
+ id: 'f0c9a6e4',
304
+ label: 'wiki.initech.dev',
305
+ active: false,
306
+ status: 'expired',
307
+ category: 'tertiary',
308
+ score: 3,
309
+ expiry: '2024-12-31T00:00:00Z',
310
+ tags: ['legacy'],
311
+ },
312
+ {
313
+ id: 'a4d1f8b6',
314
+ label: 'proxy.acme.com',
315
+ active: true,
316
+ status: 'active',
317
+ category: 'primary',
318
+ score: 83,
319
+ expiry: '2028-03-22T00:00:00Z',
320
+ tags: ['production', 'cdn'],
321
+ },
322
+ {
323
+ id: 'b9e5c0a8',
324
+ label: 'dev.globex.io',
325
+ active: true,
326
+ status: 'critical',
327
+ category: 'secondary',
328
+ score: 38,
329
+ expiry: '2026-03-25T00:00:00Z',
330
+ tags: ['staging'],
331
+ },
332
+ {
333
+ id: 'c2f6d3b7',
334
+ label: 'GTS Root R1',
335
+ active: true,
336
+ status: 'active',
337
+ category: 'primary',
338
+ score: 100,
339
+ expiry: '2036-06-22T00:00:00Z',
340
+ tags: ['infrastructure'],
341
+ },
342
+ ];
343
+
344
+ // -- Server-side filter logic -----------------------------------------------
345
+
346
+ const matchesFilter = (row: Row, item: GridFilterModel['items'][number]): boolean => {
347
+ const { field, operator, value } = item;
348
+ if (!operator) return true;
349
+
350
+ const raw = row[field as keyof Row];
351
+
352
+ // -- String operators --
353
+ if (typeof raw === 'string') {
354
+ const s = raw.toLowerCase();
355
+ const v = ((value as string) || '').toLowerCase();
356
+ switch (operator) {
357
+ case 'contains':
358
+ return s.includes(v);
359
+ case 'equals':
360
+ return s === v;
361
+ case 'startsWith':
362
+ return s.startsWith(v);
363
+ case 'endsWith':
364
+ return s.endsWith(v);
365
+ case 'isEmpty':
366
+ return s === '';
367
+ case 'isNotEmpty':
368
+ return s !== '';
369
+ case 'is':
370
+ return s === v;
371
+ case 'not':
372
+ return s !== v;
373
+ case 'isAnyOf':
374
+ return Array.isArray(value) && value.some((o: string) => o.toLowerCase() === s);
375
+ default:
376
+ return true;
377
+ }
378
+ }
379
+
380
+ // -- Number operators --
381
+ if (typeof raw === 'number') {
382
+ const n = Number(value);
383
+ switch (operator) {
384
+ case '=':
385
+ return raw === n;
386
+ case '!=':
387
+ return raw !== n;
388
+ case '>':
389
+ return raw > n;
390
+ case '>=':
391
+ return raw >= n;
392
+ case '<':
393
+ return raw < n;
394
+ case '<=':
395
+ return raw <= n;
396
+ case 'isEmpty':
397
+ return false;
398
+ case 'isNotEmpty':
399
+ return true;
400
+ default:
401
+ return true;
402
+ }
403
+ }
404
+
405
+ // -- Boolean operators --
406
+ if (typeof raw === 'boolean') {
407
+ if (operator === 'is') return String(raw) === value;
408
+ return true;
409
+ }
410
+
411
+ // -- Array (tags) operators --
412
+ if (Array.isArray(raw)) {
413
+ switch (operator) {
414
+ case 'isAnyOf':
415
+ return Array.isArray(value) && value.some((v: string) => raw.includes(v));
416
+ case 'is':
417
+ return raw.includes(value as string);
418
+ case 'not':
419
+ return !raw.includes(value as string);
420
+ case 'isEmpty':
421
+ return raw.length === 0;
422
+ case 'isNotEmpty':
423
+ return raw.length > 0;
424
+ default:
425
+ return true;
426
+ }
427
+ }
428
+
429
+ return true;
430
+ };
431
+
432
+ // -- Server-side sort logic -------------------------------------------------
433
+
434
+ const compareRows = (a: Row, b: Row, field: string, direction: 'asc' | 'desc'): number => {
435
+ let av = a[field as keyof Row];
436
+ let bv = b[field as keyof Row];
437
+
438
+ // Date fields — compare as timestamps
439
+ if (field === 'expiry') {
440
+ av = new Date(av as string).getTime() as unknown as string;
441
+ bv = new Date(bv as string).getTime() as unknown as string;
442
+ }
443
+
444
+ let cmp = 0;
445
+ if (av == null && bv == null) cmp = 0;
446
+ else if (av == null) cmp = -1;
447
+ else if (bv == null) cmp = 1;
448
+ else if (typeof av === 'string' && typeof bv === 'string') cmp = av.localeCompare(bv);
449
+ else if (typeof av === 'number' && typeof bv === 'number') cmp = av - bv;
450
+ else if (typeof av === 'boolean' && typeof bv === 'boolean') cmp = Number(av) - Number(bv);
451
+ else cmp = String(av).localeCompare(String(bv));
452
+
453
+ return direction === 'desc' ? -cmp : cmp;
454
+ };
455
+
456
+ // -- Fake fetch: applies filter → sort → paginate on the "server" -----------
457
+
458
+ type FetchParams = {
459
+ page: number;
460
+ pageSize: number;
461
+ sortModel: GridSortModel;
462
+ filterModel: GridFilterModel;
463
+ quickFilterText?: string;
464
+ };
465
+
466
+ type FetchResult = { rows: Row[]; totalRows: number };
467
+
468
+ const fakeFetch = ({ page, pageSize, sortModel, filterModel, quickFilterText }: FetchParams): Promise<FetchResult> =>
469
+ new Promise((resolve) => {
470
+ setTimeout(() => {
471
+ let filtered = [...DATABASE];
472
+
473
+ // Apply column filters
474
+ if (filterModel.items.length > 0) {
475
+ const logicOperator = filterModel.logicOperator || 'and';
476
+ filtered = filtered.filter((row) => {
477
+ const results = filterModel.items
478
+ .filter((it) => it.value !== undefined || ['isEmpty', 'isNotEmpty'].includes(it.operator || ''))
479
+ .map((it) => matchesFilter(row, it));
480
+ if (results.length === 0) return true;
481
+ return logicOperator === 'and' ? results.every(Boolean) : results.some(Boolean);
482
+ });
483
+ }
484
+
485
+ // Apply quick filter (search across all string fields)
486
+ if (quickFilterText) {
487
+ const q = quickFilterText.toLowerCase();
488
+ filtered = filtered.filter((row) =>
489
+ [row.id, row.label, row.status, row.category, ...row.tags].some((v) => v.toLowerCase().includes(q))
490
+ );
491
+ }
492
+
493
+ // Apply sorting
494
+ if (sortModel.length > 0) {
495
+ filtered.sort((a, b) => {
496
+ for (const { field, sort } of sortModel) {
497
+ const cmp = compareRows(a, b, field, sort || 'asc');
498
+ if (cmp !== 0) return cmp;
499
+ }
500
+ return 0;
501
+ });
502
+ }
503
+
504
+ const totalRows = filtered.length;
505
+ const start = page * pageSize;
506
+ const rows = filtered.slice(start, start + pageSize);
507
+
508
+ resolve({ rows, totalRows });
509
+ }, 400);
510
+ });
511
+
512
+ // -- Helpers ----------------------------------------------------------------
513
+
514
+ const statusColor = (v: string) => {
515
+ switch (v) {
516
+ case 'active':
517
+ return 'success';
518
+ case 'warning':
519
+ return 'warning';
520
+ case 'critical':
521
+ return 'error';
522
+ case 'expired':
523
+ return 'grey';
524
+ case 'revoked':
525
+ return 'error-dark';
526
+ default:
527
+ return 'grey';
528
+ }
529
+ };
530
+
531
+ const categoryIcon = (v: string) => {
532
+ switch (v) {
533
+ case 'primary':
534
+ return { icon: mdiShieldCheck, color: 'success' };
535
+ case 'secondary':
536
+ return { icon: mdiLinkVariant, color: 'info' };
537
+ case 'tertiary':
538
+ return { icon: mdiLeaf, color: 'grey' };
539
+ default:
540
+ return { icon: mdiLeaf, color: 'grey' };
541
+ }
542
+ };
543
+
544
+ const formatDate = (d: Date) => (isNaN(d.getTime()) ? '—' : d.toISOString().replace('T', ' ').slice(0, 19));
545
+
546
+ // -- Columns ----------------------------------------------------------------
547
+
548
+ const columns: GridColDef[] = [
549
+ {
550
+ field: 'id',
551
+ headerName: 'ID',
552
+ width: 100,
553
+ type: 'rsString' as string,
554
+ renderCell: ({ value }: GridRenderCellParams) => <TextCell style={{ fontFamily: 'monospace' }}>{value}</TextCell>,
555
+ },
556
+ {
557
+ field: 'label',
558
+ headerName: 'Label',
559
+ flex: 2,
560
+ type: 'rsString' as string,
561
+ renderCell: ({ value }: GridRenderCellParams) => <TextCell>{value}</TextCell>,
562
+ },
563
+ {
564
+ field: 'active',
565
+ headerName: 'Active',
566
+ width: 80,
567
+ type: 'boolean',
568
+ renderCell: ({ value }: GridRenderCellParams) =>
569
+ value ? (
570
+ <Icon icon={mdiCheck} color="success" size="small" />
571
+ ) : (
572
+ <Icon icon={mdiClose} color="grey" size="small" />
573
+ ),
574
+ },
575
+ {
576
+ field: 'status',
577
+ headerName: 'Status',
578
+ flex: 1,
579
+ type: 'rsSingleSelect' as string,
580
+ valueOptions: [
581
+ { value: 'active', label: 'Active' },
582
+ { value: 'warning', label: 'Warning' },
583
+ { value: 'critical', label: 'Critical' },
584
+ { value: 'expired', label: 'Expired' },
585
+ { value: 'revoked', label: 'Revoked' },
586
+ ],
587
+ renderCell: ({ value }: GridRenderCellParams) => <Pill color={statusColor(value)}>{value}</Pill>,
588
+ } as GridColDef,
589
+ {
590
+ field: 'category',
591
+ headerName: 'Category',
592
+ width: 110,
593
+ type: 'rsSingleSelect' as string,
594
+ valueOptions: [
595
+ { value: 'primary', label: 'Primary' },
596
+ { value: 'secondary', label: 'Secondary' },
597
+ { value: 'tertiary', label: 'Tertiary' },
598
+ ],
599
+ renderCell: ({ value }: GridRenderCellParams) => {
600
+ const cfg = categoryIcon(value);
601
+ return <Icon icon={cfg.icon} color={cfg.color} size="small" />;
602
+ },
603
+ } as GridColDef,
604
+ {
605
+ field: 'score',
606
+ headerName: 'Score',
607
+ width: 80,
608
+ type: 'rsNumber' as string,
609
+ },
610
+ {
611
+ field: 'expiry',
612
+ headerName: 'Expiry',
613
+ width: 180,
614
+ type: 'date',
615
+ valueGetter: (value: unknown) => new Date(value as string),
616
+ renderCell: ({ value }: GridRenderCellParams) => {
617
+ if (!value) return <TextCell>—</TextCell>;
618
+ const d = value as Date;
619
+ const isPast = d < new Date();
620
+ return <TextCell style={isPast ? { color: 'var(--redsift-color-red-n)' } : undefined}>{formatDate(d)}</TextCell>;
621
+ },
622
+ },
623
+ {
624
+ field: 'tags',
625
+ headerName: 'Tags',
626
+ flex: 2,
627
+ type: 'rsMultipleSelect' as string,
628
+ valueOptions: TAG_OPTIONS,
629
+ sortable: false,
630
+ renderCell: ({ value }: GridRenderCellParams) => {
631
+ const arr = value as string[];
632
+ if (!arr || arr.length === 0) return <TextCell>—</TextCell>;
633
+ return (
634
+ <Flexbox gap="4px" flexWrap="wrap" alignItems="center">
635
+ {arr.map((tag: string) => (
636
+ <Pill key={tag} color="blue">
637
+ {tag}
638
+ </Pill>
639
+ ))}
640
+ </Flexbox>
641
+ );
642
+ },
643
+ } as GridColDef,
644
+ {
645
+ field: 'actions',
646
+ headerName: '',
647
+ type: 'actions',
648
+ width: 56,
649
+ hideable: false,
650
+ sortable: false,
651
+ filterable: false,
652
+ renderCell: ({ row }: GridRenderCellParams) => (
653
+ <IconButtonLink
654
+ href="/patterns/server-datagrid-page"
655
+ icon={mdiArrowRight}
656
+ color="info"
657
+ aria-label={`View ${row.label}`}
658
+ />
659
+ ),
660
+ },
661
+ ];
662
+
663
+ // -- Component --------------------------------------------------------------
664
+
665
+ export default () => {
666
+ const [rows, setRows] = useState<Row[]>([]);
667
+ const [totalRows, setTotalRows] = useState(0);
668
+ const [loading, setLoading] = useState(true);
669
+ const [page, setPage] = useState(0);
670
+ const [pageSize, setPageSize] = useState(10);
671
+ const [sortModel, setSortModel] = useState<GridSortModel>([]);
672
+ const [filterModel, setFilterModel] = useState<GridFilterModel>({ items: [] });
673
+ const [selectionModel, setSelectionModel] = useState<GridRowSelectionModel>([]);
674
+
675
+ // Track the quick filter text extracted from filterModel
676
+ const quickFilterText = (filterModel as { quickFilterValues?: string[] }).quickFilterValues?.join(' ') || '';
677
+
678
+ // Debounce ref for filter changes
679
+ const debounceRef = useRef<ReturnType<typeof setTimeout>>();
680
+
681
+ const fetchData = useCallback(() => {
682
+ setLoading(true);
683
+ fakeFetch({ page, pageSize, sortModel, filterModel, quickFilterText }).then(({ rows: r, totalRows: t }) => {
684
+ setRows(r);
685
+ setTotalRows(t);
686
+ setLoading(false);
687
+ });
688
+ }, [page, pageSize, sortModel, filterModel, quickFilterText]);
689
+
690
+ // Fetch on mount and whenever params change
691
+ useEffect(() => {
692
+ fetchData();
693
+ }, [fetchData]);
694
+
695
+ // Debounced filter handler
696
+ const handleFilterModelChange = useCallback((model: GridFilterModel) => {
697
+ if (debounceRef.current) clearTimeout(debounceRef.current);
698
+ debounceRef.current = setTimeout(() => {
699
+ setFilterModel(model);
700
+ setPage(0); // Reset to first page on filter change
701
+ }, 300);
702
+ }, []);
703
+
704
+ const handleSortModelChange = useCallback((model: GridSortModel) => {
705
+ setSortModel(model);
706
+ setPage(0);
707
+ }, []);
708
+
709
+ return (
710
+ <div style={{ width: '100%' }}>
711
+ <Flexbox flexDirection="column" gap="0px">
712
+ {/* Bulk actions bar — visible when rows are selected */}
713
+ {selectionModel.length > 0 && (
714
+ <Flexbox gap="8px" alignItems="center" style={{ padding: '8px 0' }}>
715
+ <Pill color="info">{selectionModel.length} selected</Pill>
716
+ <Button variant="secondary" onClick={() => console.log('Selected:', selectionModel)}>
717
+ Log Selection
718
+ </Button>
719
+ <Button variant="secondary" color="error" onClick={() => console.log('Delete:', selectionModel)}>
720
+ Delete
721
+ </Button>
722
+ </Flexbox>
723
+ )}
724
+
725
+ <DataGrid
726
+ autoHeight
727
+ pagination
728
+ paginationMode="server"
729
+ sortingMode="server"
730
+ filterMode="server"
731
+ loading={loading}
732
+ rows={rows}
733
+ columns={columns}
734
+ rowCount={totalRows}
735
+ paginationModel={{ page, pageSize }}
736
+ pageSizeOptions={[10, 25, 50]}
737
+ onPaginationModelChange={(model) => {
738
+ if (model.pageSize !== pageSize) {
739
+ setPageSize(model.pageSize);
740
+ setPage(0);
741
+ } else {
742
+ setPage(model.page);
743
+ }
744
+ }}
745
+ sortModel={sortModel}
746
+ onSortModelChange={handleSortModelChange}
747
+ filterModel={filterModel}
748
+ onFilterModelChange={handleFilterModelChange}
749
+ checkboxSelection
750
+ rowSelectionModel={selectionModel}
751
+ onRowSelectionModelChange={setSelectionModel}
752
+ slots={{ toolbar: CustomToolbar }}
753
+ />
754
+ </Flexbox>
755
+ </div>
756
+ );
757
+ };