@gntik-ai/blocks 0.0.0-stage → 0.6.0

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 (397) hide show
  1. package/dist/index.d.ts +3626 -0
  2. package/dist/index.js +15417 -0
  3. package/dist/index.js.map +1 -0
  4. package/package.json +62 -3
  5. package/src/ai/AssistantCardGrid/AssistantCardGrid.test.tsx +32 -0
  6. package/src/ai/AssistantCardGrid/AssistantCardGrid.tsx +98 -0
  7. package/src/ai/AssistantCardGrid/block.meta.ts +10 -0
  8. package/src/ai/AssistantCardGrid/fixtures.ts +72 -0
  9. package/src/ai/EvaluationScorecard/EvaluationScorecard.test.tsx +39 -0
  10. package/src/ai/EvaluationScorecard/EvaluationScorecard.tsx +233 -0
  11. package/src/ai/EvaluationScorecard/block.meta.ts +10 -0
  12. package/src/ai/EvaluationScorecard/fixtures.ts +79 -0
  13. package/src/ai/ModelKeysList/ModelKeysList.test.tsx +44 -0
  14. package/src/ai/ModelKeysList/ModelKeysList.tsx +203 -0
  15. package/src/ai/ModelKeysList/block.meta.ts +10 -0
  16. package/src/ai/ModelKeysList/fixtures.ts +44 -0
  17. package/src/ai/PromptEditor/PromptEditor.test.tsx +97 -0
  18. package/src/ai/PromptEditor/PromptEditor.tsx +202 -0
  19. package/src/ai/PromptEditor/block.meta.ts +10 -0
  20. package/src/ai/PromptEditor/examples/controlled-variables.tsx +23 -0
  21. package/src/ai/PromptEditor/fixtures.ts +12 -0
  22. package/src/ai/TokenCostCard/TokenCostCard.test.tsx +36 -0
  23. package/src/ai/TokenCostCard/TokenCostCard.tsx +130 -0
  24. package/src/ai/TokenCostCard/block.meta.ts +9 -0
  25. package/src/ai/TokenCostCard/fixtures.ts +18 -0
  26. package/src/ai/TraceWaterfall/TraceWaterfall.test.tsx +119 -0
  27. package/src/ai/TraceWaterfall/TraceWaterfall.tsx +317 -0
  28. package/src/ai/TraceWaterfall/block.meta.ts +10 -0
  29. package/src/ai/TraceWaterfall/examples/no-details.tsx +6 -0
  30. package/src/ai/TraceWaterfall/examples/running.tsx +16 -0
  31. package/src/ai/TraceWaterfall/fixtures.ts +133 -0
  32. package/src/ai/TraceWaterfall/span-details.tsx +51 -0
  33. package/src/ai/index.ts +13 -0
  34. package/src/auth/ForgotPasswordForm/ForgotPasswordForm.test.tsx +209 -0
  35. package/src/auth/ForgotPasswordForm/ForgotPasswordForm.tsx +211 -0
  36. package/src/auth/ForgotPasswordForm/block.meta.ts +10 -0
  37. package/src/auth/ForgotPasswordForm/examples/UsernameOrEmail.tsx +24 -0
  38. package/src/auth/MfaChallenge/MfaChallenge.test.tsx +35 -0
  39. package/src/auth/MfaChallenge/MfaChallenge.tsx +170 -0
  40. package/src/auth/MfaChallenge/block.meta.ts +9 -0
  41. package/src/auth/ResetPasswordForm/ResetPasswordForm.test.tsx +27 -0
  42. package/src/auth/ResetPasswordForm/ResetPasswordForm.tsx +100 -0
  43. package/src/auth/ResetPasswordForm/block.meta.ts +10 -0
  44. package/src/auth/SignInForm/SignInForm.test.tsx +222 -0
  45. package/src/auth/SignInForm/SignInForm.tsx +226 -0
  46. package/src/auth/SignInForm/block.meta.ts +22 -0
  47. package/src/auth/SignInForm/examples/Username.tsx +26 -0
  48. package/src/auth/SignUpForm/SignUpForm.configured.test.tsx +316 -0
  49. package/src/auth/SignUpForm/SignUpForm.test.tsx +33 -0
  50. package/src/auth/SignUpForm/SignUpForm.tsx +298 -0
  51. package/src/auth/SignUpForm/block.meta.ts +22 -0
  52. package/src/auth/SignUpForm/examples/CustomFields.tsx +56 -0
  53. package/src/auth/SignUpForm/examples/Error.tsx +5 -0
  54. package/src/auth/SignUpForm/examples/Loading.tsx +6 -0
  55. package/src/auth/SignUpForm/examples/NoTerms.tsx +6 -0
  56. package/src/auth/SignUpForm/examples/Submitting.tsx +5 -0
  57. package/src/auth/SignUpForm/examples/Unavailable.tsx +15 -0
  58. package/src/auth/SsoButtons/SsoButtons.test.tsx +23 -0
  59. package/src/auth/SsoButtons/SsoButtons.tsx +52 -0
  60. package/src/auth/SsoButtons/block.meta.ts +9 -0
  61. package/src/auth/SsoButtons/fixtures.ts +15 -0
  62. package/src/auth/index.ts +16 -0
  63. package/src/auth/shared.tsx +147 -0
  64. package/src/billing/CostBreakdown/CostBreakdown.test.tsx +24 -0
  65. package/src/billing/CostBreakdown/CostBreakdown.tsx +95 -0
  66. package/src/billing/CostBreakdown/block.meta.ts +10 -0
  67. package/src/billing/CostBreakdown/fixtures.ts +9 -0
  68. package/src/billing/InvoiceTable/InvoiceTable.test.tsx +29 -0
  69. package/src/billing/InvoiceTable/InvoiceTable.tsx +130 -0
  70. package/src/billing/InvoiceTable/block.meta.ts +10 -0
  71. package/src/billing/InvoiceTable/fixtures.ts +38 -0
  72. package/src/billing/PaymentMethodCard/PaymentMethodCard.test.tsx +28 -0
  73. package/src/billing/PaymentMethodCard/PaymentMethodCard.tsx +103 -0
  74. package/src/billing/PaymentMethodCard/block.meta.ts +10 -0
  75. package/src/billing/PaymentMethodCard/fixtures.ts +12 -0
  76. package/src/billing/PlanCard/PlanCard.baseline.test.tsx +26 -0
  77. package/src/billing/PlanCard/PlanCard.summary.test.tsx +227 -0
  78. package/src/billing/PlanCard/PlanCard.test.tsx +27 -0
  79. package/src/billing/PlanCard/PlanCard.tsx +140 -0
  80. package/src/billing/PlanCard/PlanSummary.tsx +101 -0
  81. package/src/billing/PlanCard/README.md +23 -0
  82. package/src/billing/PlanCard/__snapshots__/PlanCard.baseline.test.tsx.snap +5 -0
  83. package/src/billing/PlanCard/block.meta.ts +60 -0
  84. package/src/billing/PlanCard/examples/SummaryActionsOnly.tsx +12 -0
  85. package/src/billing/PlanCard/examples/SummaryFeaturesOnly.tsx +14 -0
  86. package/src/billing/PlanCard/examples/SummaryFull.tsx +28 -0
  87. package/src/billing/PlanCard/examples/SummaryMinimal.tsx +5 -0
  88. package/src/billing/PlanCard/examples/SummaryPriceOnly.tsx +15 -0
  89. package/src/billing/PlanCard/examples/SummaryRenewalOnly.tsx +14 -0
  90. package/src/billing/PlanCard/examples/SummaryUnassigned.tsx +5 -0
  91. package/src/billing/PlanCard/examples/SummaryUnassignedNoLabel.tsx +5 -0
  92. package/src/billing/PlanCard/examples/SummaryZeroPrice.tsx +15 -0
  93. package/src/billing/PlanCard/fixtures.ts +19 -0
  94. package/src/billing/PlanDefinitionCard/PlanDefinitionCard.test.tsx +111 -0
  95. package/src/billing/PlanDefinitionCard/PlanDefinitionCard.tsx +86 -0
  96. package/src/billing/PlanDefinitionCard/README.md +29 -0
  97. package/src/billing/PlanDefinitionCard/block.meta.ts +46 -0
  98. package/src/billing/PlanDefinitionCard/examples/Compact.tsx +23 -0
  99. package/src/billing/PlanDefinitionCard/examples/Full.tsx +23 -0
  100. package/src/billing/PlanDefinitionCard/fixtures.ts +12 -0
  101. package/src/billing/QuotaMeters/QuotaMeters.test.tsx +23 -0
  102. package/src/billing/QuotaMeters/QuotaMeters.tsx +92 -0
  103. package/src/billing/QuotaMeters/block.meta.ts +10 -0
  104. package/src/billing/QuotaMeters/fixtures.ts +9 -0
  105. package/src/billing/SpendVsBudget/SpendVsBudget.test.tsx +24 -0
  106. package/src/billing/SpendVsBudget/SpendVsBudget.tsx +105 -0
  107. package/src/billing/SpendVsBudget/block.meta.ts +10 -0
  108. package/src/billing/SpendVsBudget/fixtures.ts +18 -0
  109. package/src/billing/format.ts +42 -0
  110. package/src/billing/index.ts +21 -0
  111. package/src/builders/CodePanel/CodePanel.test.tsx +280 -0
  112. package/src/builders/CodePanel/CodePanel.tsx +239 -0
  113. package/src/builders/CodePanel/block.meta.ts +10 -0
  114. package/src/builders/CodePanel/examples/controlled.tsx +15 -0
  115. package/src/builders/CodePanel/examples/diagnostics.tsx +40 -0
  116. package/src/builders/CodePanel/fixtures.ts +95 -0
  117. package/src/builders/CodePanel/marker-store.ts +90 -0
  118. package/src/builders/CodePanel/markers.ts +62 -0
  119. package/src/builders/FlowBuilder/FlowBuilder.test.tsx +480 -0
  120. package/src/builders/FlowBuilder/FlowBuilder.tsx +343 -0
  121. package/src/builders/FlowBuilder/block.meta.ts +20 -0
  122. package/src/builders/FlowBuilder/connections.ts +67 -0
  123. package/src/builders/FlowBuilder/decorated-nodes.ts +80 -0
  124. package/src/builders/FlowBuilder/examples/extensions.tsx +120 -0
  125. package/src/builders/FlowBuilder/extensions.ts +37 -0
  126. package/src/builders/FlowBuilder/fixtures.ts +80 -0
  127. package/src/builders/FlowBuilder/parts.tsx +196 -0
  128. package/src/builders/FlowBuilder/problems.tsx +40 -0
  129. package/src/builders/LogViewer/LogViewer.test.tsx +97 -0
  130. package/src/builders/LogViewer/LogViewer.tsx +253 -0
  131. package/src/builders/LogViewer/block.meta.ts +10 -0
  132. package/src/builders/LogViewer/fixtures.ts +49 -0
  133. package/src/builders/index.ts +15 -0
  134. package/src/data-display/ActivityFeed/ActivityFeed.test.tsx +62 -0
  135. package/src/data-display/ActivityFeed/ActivityFeed.tsx +118 -0
  136. package/src/data-display/ActivityFeed/block.meta.ts +10 -0
  137. package/src/data-display/ActivityFeed/fixtures.ts +29 -0
  138. package/src/data-display/ChartCard/ChartCard.ranges.test.tsx +282 -0
  139. package/src/data-display/ChartCard/ChartCard.test.tsx +54 -0
  140. package/src/data-display/ChartCard/ChartCard.tsx +218 -0
  141. package/src/data-display/ChartCard/block.meta.ts +10 -0
  142. package/src/data-display/ChartCard/examples/custom-ranges.tsx +37 -0
  143. package/src/data-display/ChartCard/examples/heatmap.tsx +20 -0
  144. package/src/data-display/ChartCard/examples/toolbar-owned-range.tsx +34 -0
  145. package/src/data-display/ChartCard/fixtures.ts +82 -0
  146. package/src/data-display/CommentThread/CommentThread.test.tsx +29 -0
  147. package/src/data-display/CommentThread/CommentThread.tsx +120 -0
  148. package/src/data-display/CommentThread/block.meta.ts +10 -0
  149. package/src/data-display/CommentThread/fixtures.ts +28 -0
  150. package/src/data-display/DescriptionListCard/DescriptionListCard.test.tsx +28 -0
  151. package/src/data-display/DescriptionListCard/DescriptionListCard.tsx +120 -0
  152. package/src/data-display/DescriptionListCard/block.meta.ts +9 -0
  153. package/src/data-display/DescriptionListCard/fixtures.ts +12 -0
  154. package/src/data-display/KpiRow/KpiRow.test.tsx +29 -0
  155. package/src/data-display/KpiRow/KpiRow.tsx +101 -0
  156. package/src/data-display/KpiRow/block.meta.ts +10 -0
  157. package/src/data-display/KpiRow/fixtures.ts +34 -0
  158. package/src/data-display/ResourceCardGrid/ResourceCardGrid.test.tsx +22 -0
  159. package/src/data-display/ResourceCardGrid/ResourceCardGrid.tsx +98 -0
  160. package/src/data-display/ResourceCardGrid/block.meta.ts +10 -0
  161. package/src/data-display/ResourceCardGrid/fixtures.ts +72 -0
  162. package/src/data-display/StatCard/StatCard.test.tsx +30 -0
  163. package/src/data-display/StatCard/StatCard.tsx +138 -0
  164. package/src/data-display/StatCard/block.meta.ts +10 -0
  165. package/src/data-display/StatCard/fixtures.ts +26 -0
  166. package/src/data-display/StatusTimeline/StatusTimeline.test.tsx +153 -0
  167. package/src/data-display/StatusTimeline/StatusTimeline.tsx +114 -0
  168. package/src/data-display/StatusTimeline/block.meta.ts +10 -0
  169. package/src/data-display/StatusTimeline/examples/Empty.tsx +6 -0
  170. package/src/data-display/StatusTimeline/fixtures.ts +62 -0
  171. package/src/data-display/index.ts +34 -0
  172. package/src/data-display/shared/TrendDelta.tsx +60 -0
  173. package/src/feedback/ConfirmDestructive/ConfirmDestructive.test.tsx +47 -0
  174. package/src/feedback/ConfirmDestructive/ConfirmDestructive.tsx +145 -0
  175. package/src/feedback/ConfirmDestructive/block.meta.ts +10 -0
  176. package/src/feedback/EmptyStates/EmptyStates.test.tsx +48 -0
  177. package/src/feedback/EmptyStates/EmptyStates.tsx +191 -0
  178. package/src/feedback/EmptyStates/block.meta.ts +10 -0
  179. package/src/feedback/EmptyStates/fixtures.ts +6 -0
  180. package/src/feedback/ErrorPanel/ErrorPanel.test.tsx +37 -0
  181. package/src/feedback/ErrorPanel/ErrorPanel.tsx +146 -0
  182. package/src/feedback/ErrorPanel/block.meta.ts +10 -0
  183. package/src/feedback/InlineCallout/InlineCallout.test.tsx +33 -0
  184. package/src/feedback/InlineCallout/InlineCallout.tsx +81 -0
  185. package/src/feedback/InlineCallout/block.meta.ts +9 -0
  186. package/src/feedback/Skeletons/Skeletons.test.tsx +25 -0
  187. package/src/feedback/Skeletons/Skeletons.tsx +142 -0
  188. package/src/feedback/Skeletons/block.meta.ts +10 -0
  189. package/src/feedback/SystemBanner/SystemBanner.test.tsx +29 -0
  190. package/src/feedback/SystemBanner/SystemBanner.tsx +105 -0
  191. package/src/feedback/SystemBanner/block.meta.ts +9 -0
  192. package/src/feedback/index.ts +14 -0
  193. package/src/feedback/types.ts +13 -0
  194. package/src/forms/CreateApiKeyDialog/CreateApiKeyDialog.test.tsx +36 -0
  195. package/src/forms/CreateApiKeyDialog/CreateApiKeyDialog.tsx +197 -0
  196. package/src/forms/CreateApiKeyDialog/block.meta.ts +10 -0
  197. package/src/forms/CreateApiKeyDialog/fixtures.ts +31 -0
  198. package/src/forms/CredentialDisclosureDialog/CredentialDisclosureDialog.test.tsx +339 -0
  199. package/src/forms/CredentialDisclosureDialog/CredentialDisclosureDialog.tsx +193 -0
  200. package/src/forms/CredentialDisclosureDialog/block.meta.ts +10 -0
  201. package/src/forms/CredentialDisclosureDialog/examples/Fresh.tsx +32 -0
  202. package/src/forms/CredentialDisclosureDialog/examples/ResponseGating.tsx +69 -0
  203. package/src/forms/CredentialDisclosureDialog/examples/Stored.tsx +32 -0
  204. package/src/forms/CredentialDisclosureDialog/fixtures.ts +14 -0
  205. package/src/forms/DangerZone/ConfirmDestructiveDialog.tsx +101 -0
  206. package/src/forms/DangerZone/DangerZone.test.tsx +28 -0
  207. package/src/forms/DangerZone/DangerZone.tsx +86 -0
  208. package/src/forms/DangerZone/block.meta.ts +10 -0
  209. package/src/forms/DangerZone/fixtures.ts +36 -0
  210. package/src/forms/DateRangeFilter/DateRangeFilter.test.tsx +26 -0
  211. package/src/forms/DateRangeFilter/DateRangeFilter.tsx +83 -0
  212. package/src/forms/DateRangeFilter/block.meta.ts +9 -0
  213. package/src/forms/DialogWizard/DialogWizard.test.tsx +95 -0
  214. package/src/forms/DialogWizard/DialogWizard.tsx +39 -0
  215. package/src/forms/DialogWizard/block.meta.ts +10 -0
  216. package/src/forms/DialogWizard/examples/CreateResource.tsx +65 -0
  217. package/src/forms/DialogWizard/fixtures.ts +20 -0
  218. package/src/forms/FileUploadPanel/FileUploadPanel.test.tsx +43 -0
  219. package/src/forms/FileUploadPanel/FileUploadPanel.tsx +244 -0
  220. package/src/forms/FileUploadPanel/block.meta.ts +10 -0
  221. package/src/forms/FileUploadPanel/fixtures.ts +66 -0
  222. package/src/forms/FormSection/FormSection.test.tsx +34 -0
  223. package/src/forms/FormSection/FormSection.tsx +77 -0
  224. package/src/forms/FormSection/block.meta.ts +9 -0
  225. package/src/forms/InviteMembersDialog/InviteMembersDialog.test.tsx +37 -0
  226. package/src/forms/InviteMembersDialog/InviteMembersDialog.tsx +183 -0
  227. package/src/forms/InviteMembersDialog/block.meta.ts +10 -0
  228. package/src/forms/InviteMembersDialog/fixtures.ts +14 -0
  229. package/src/forms/SettingsRow/SettingsRow.test.tsx +50 -0
  230. package/src/forms/SettingsRow/SettingsRow.tsx +74 -0
  231. package/src/forms/SettingsRow/block.meta.ts +9 -0
  232. package/src/forms/ValidationSummary/ValidationSummary.test.tsx +46 -0
  233. package/src/forms/ValidationSummary/ValidationSummary.tsx +75 -0
  234. package/src/forms/ValidationSummary/block.meta.ts +9 -0
  235. package/src/forms/ValidationSummary/fixtures.ts +13 -0
  236. package/src/forms/index.ts +27 -0
  237. package/src/index.ts +14 -0
  238. package/src/marketing/ComparisonTable/ComparisonTable.test.tsx +69 -0
  239. package/src/marketing/ComparisonTable/ComparisonTable.tsx +245 -0
  240. package/src/marketing/ComparisonTable/block.meta.ts +10 -0
  241. package/src/marketing/ComparisonTable/examples/options.tsx +48 -0
  242. package/src/marketing/ComparisonTable/fixtures.ts +83 -0
  243. package/src/marketing/Faq/Faq.test.tsx +25 -0
  244. package/src/marketing/Faq/Faq.tsx +68 -0
  245. package/src/marketing/Faq/block.meta.ts +10 -0
  246. package/src/marketing/Faq/fixtures.ts +32 -0
  247. package/src/marketing/FeatureGrid/FeatureGrid.test.tsx +19 -0
  248. package/src/marketing/FeatureGrid/FeatureGrid.tsx +79 -0
  249. package/src/marketing/FeatureGrid/block.meta.ts +10 -0
  250. package/src/marketing/FeatureGrid/fixtures.ts +36 -0
  251. package/src/marketing/Hero/Hero.test.tsx +33 -0
  252. package/src/marketing/Hero/Hero.tsx +107 -0
  253. package/src/marketing/Hero/block.meta.ts +10 -0
  254. package/src/marketing/PricingTable/PricingTable.test.tsx +92 -0
  255. package/src/marketing/PricingTable/PricingTable.tsx +323 -0
  256. package/src/marketing/PricingTable/block.meta.ts +10 -0
  257. package/src/marketing/PricingTable/fixtures.ts +34 -0
  258. package/src/marketing/actions.tsx +40 -0
  259. package/src/marketing/index.ts +15 -0
  260. package/src/meta.ts +25 -0
  261. package/src/page-chrome/BulkActionBar/BulkActionBar.test.tsx +34 -0
  262. package/src/page-chrome/BulkActionBar/BulkActionBar.tsx +76 -0
  263. package/src/page-chrome/BulkActionBar/block.meta.ts +9 -0
  264. package/src/page-chrome/PageHeader/PageHeader.test.tsx +68 -0
  265. package/src/page-chrome/PageHeader/PageHeader.tsx +152 -0
  266. package/src/page-chrome/PageHeader/block.meta.ts +10 -0
  267. package/src/page-chrome/PageHeader/fixtures.ts +29 -0
  268. package/src/page-chrome/PageToolbar/PageToolbar.test.tsx +38 -0
  269. package/src/page-chrome/PageToolbar/PageToolbar.tsx +138 -0
  270. package/src/page-chrome/PageToolbar/block.meta.ts +10 -0
  271. package/src/page-chrome/PageToolbar/fixtures.ts +8 -0
  272. package/src/page-chrome/SectionHeader/SectionHeader.test.tsx +28 -0
  273. package/src/page-chrome/SectionHeader/SectionHeader.tsx +73 -0
  274. package/src/page-chrome/SectionHeader/block.meta.ts +9 -0
  275. package/src/page-chrome/StickyActionBar/StickyActionBar.test.tsx +29 -0
  276. package/src/page-chrome/StickyActionBar/StickyActionBar.tsx +66 -0
  277. package/src/page-chrome/StickyActionBar/block.meta.ts +9 -0
  278. package/src/page-chrome/index.ts +13 -0
  279. package/src/page-chrome/types.ts +25 -0
  280. package/src/scheduling/AgendaList/AgendaList.test.tsx +28 -0
  281. package/src/scheduling/AgendaList/AgendaList.tsx +165 -0
  282. package/src/scheduling/AgendaList/block.meta.ts +10 -0
  283. package/src/scheduling/MonthCalendar/MonthCalendar.test.tsx +32 -0
  284. package/src/scheduling/MonthCalendar/MonthCalendar.tsx +223 -0
  285. package/src/scheduling/MonthCalendar/block.meta.ts +10 -0
  286. package/src/scheduling/TimeSlotPicker/TimeSlotPicker.test.tsx +26 -0
  287. package/src/scheduling/TimeSlotPicker/TimeSlotPicker.tsx +115 -0
  288. package/src/scheduling/TimeSlotPicker/block.meta.ts +10 -0
  289. package/src/scheduling/TimeSlotPicker/fixtures.ts +10 -0
  290. package/src/scheduling/WeekCalendar/WeekCalendar.test.tsx +27 -0
  291. package/src/scheduling/WeekCalendar/WeekCalendar.tsx +274 -0
  292. package/src/scheduling/WeekCalendar/block.meta.ts +10 -0
  293. package/src/scheduling/index.ts +11 -0
  294. package/src/scheduling/shared/CalendarHeader.tsx +54 -0
  295. package/src/scheduling/shared/EventTile.tsx +21 -0
  296. package/src/scheduling/shared/dates.ts +98 -0
  297. package/src/scheduling/shared/events.ts +90 -0
  298. package/src/scheduling/shared/fixtures.ts +54 -0
  299. package/src/shell/AppSidebar/AppSidebar.test.tsx +38 -0
  300. package/src/shell/AppSidebar/AppSidebar.tsx +226 -0
  301. package/src/shell/AppSidebar/block.meta.ts +10 -0
  302. package/src/shell/AppSidebar/fixtures.ts +58 -0
  303. package/src/shell/AppTopbar/AppTopbar.test.tsx +41 -0
  304. package/src/shell/AppTopbar/AppTopbar.tsx +82 -0
  305. package/src/shell/AppTopbar/block.meta.ts +19 -0
  306. package/src/shell/AppTopbar/fixtures.ts +22 -0
  307. package/src/shell/EnvironmentBadge/EnvironmentBadge.test.tsx +22 -0
  308. package/src/shell/EnvironmentBadge/EnvironmentBadge.tsx +55 -0
  309. package/src/shell/EnvironmentBadge/block.meta.ts +10 -0
  310. package/src/shell/GlobalSearch/GlobalSearch.test.tsx +28 -0
  311. package/src/shell/GlobalSearch/GlobalSearch.tsx +80 -0
  312. package/src/shell/GlobalSearch/block.meta.ts +10 -0
  313. package/src/shell/GlobalSearch/fixtures.ts +64 -0
  314. package/src/shell/index.ts +11 -0
  315. package/src/styles.css +2 -0
  316. package/src/tables/DataTable/DataTable.edit-views.test.tsx +249 -0
  317. package/src/tables/DataTable/DataTable.labels.test.tsx +291 -0
  318. package/src/tables/DataTable/DataTable.layout.test.tsx +188 -0
  319. package/src/tables/DataTable/DataTable.overflow-labels.test.tsx +59 -0
  320. package/src/tables/DataTable/DataTable.single-select.test.tsx +501 -0
  321. package/src/tables/DataTable/DataTable.test.tsx +268 -0
  322. package/src/tables/DataTable/DataTable.tsx +531 -0
  323. package/src/tables/DataTable/block.meta.ts +129 -0
  324. package/src/tables/DataTable/column-menu.tsx +112 -0
  325. package/src/tables/DataTable/column-sizing.ts +166 -0
  326. package/src/tables/DataTable/data-table-body.tsx +195 -0
  327. package/src/tables/DataTable/data-table-props.ts +159 -0
  328. package/src/tables/DataTable/data-table-row.tsx +231 -0
  329. package/src/tables/DataTable/data-table-utils.ts +195 -0
  330. package/src/tables/DataTable/density-toggle.tsx +33 -0
  331. package/src/tables/DataTable/editable-cell.tsx +287 -0
  332. package/src/tables/DataTable/examples/editable.tsx +71 -0
  333. package/src/tables/DataTable/examples/grouped.tsx +18 -0
  334. package/src/tables/DataTable/examples/resizable-pinned.tsx +20 -0
  335. package/src/tables/DataTable/examples/saved-views.tsx +61 -0
  336. package/src/tables/DataTable/examples/single-selection.tsx +55 -0
  337. package/src/tables/DataTable/examples/virtualized.tsx +20 -0
  338. package/src/tables/DataTable/fixtures.ts +119 -0
  339. package/src/tables/DataTable/header-cell.tsx +90 -0
  340. package/src/tables/DataTable/labels.ts +54 -0
  341. package/src/tables/DataTable/resize-handle.tsx +91 -0
  342. package/src/tables/DataTable/single-selection.tsx +149 -0
  343. package/src/tables/DataTable/use-table-views.ts +79 -0
  344. package/src/tables/DataTable/use-virtual-rows.ts +83 -0
  345. package/src/tables/DataTable/views.tsx +193 -0
  346. package/src/tables/FilterBar/FilterBar.labels.test.tsx +69 -0
  347. package/src/tables/FilterBar/FilterBar.test.tsx +43 -0
  348. package/src/tables/FilterBar/FilterBar.tsx +232 -0
  349. package/src/tables/FilterBar/block.meta.ts +32 -0
  350. package/src/tables/FilterBar/fixtures.ts +32 -0
  351. package/src/tables/InlineEditRow/InlineEditRow.test.tsx +41 -0
  352. package/src/tables/InlineEditRow/InlineEditRow.tsx +215 -0
  353. package/src/tables/InlineEditRow/block.meta.ts +10 -0
  354. package/src/tables/InlineEditRow/fixtures.ts +28 -0
  355. package/src/tables/PaginationFooter/PaginationFooter.labels.test.tsx +57 -0
  356. package/src/tables/PaginationFooter/PaginationFooter.test.tsx +29 -0
  357. package/src/tables/PaginationFooter/PaginationFooter.tsx +118 -0
  358. package/src/tables/PaginationFooter/block.meta.ts +33 -0
  359. package/src/tables/SubordinateList/SubordinateList.test.tsx +351 -0
  360. package/src/tables/SubordinateList/SubordinateList.tsx +140 -0
  361. package/src/tables/SubordinateList/block.meta.ts +10 -0
  362. package/src/tables/SubordinateList/examples/ResourceForm.tsx +129 -0
  363. package/src/tables/SubordinateList/examples/empty.tsx +5 -0
  364. package/src/tables/SubordinateList/examples/error.tsx +5 -0
  365. package/src/tables/SubordinateList/examples/loading.tsx +5 -0
  366. package/src/tables/TruncatedCollectionFooter/TruncatedCollectionFooter.test.tsx +159 -0
  367. package/src/tables/TruncatedCollectionFooter/TruncatedCollectionFooter.tsx +60 -0
  368. package/src/tables/TruncatedCollectionFooter/block.meta.ts +10 -0
  369. package/src/tables/TruncatedCollectionFooter/examples/exact.tsx +6 -0
  370. package/src/tables/TruncatedCollectionFooter/examples/has-more.tsx +12 -0
  371. package/src/tables/TruncatedCollectionFooter/fixtures.ts +2 -0
  372. package/src/tables/index.ts +45 -0
  373. package/src/team/MembersTable/MembersTable.test.tsx +29 -0
  374. package/src/team/MembersTable/MembersTable.tsx +141 -0
  375. package/src/team/MembersTable/block.meta.ts +9 -0
  376. package/src/team/MembersTable/fixtures.ts +19 -0
  377. package/src/team/NotificationMatrix/NotificationMatrix.test.tsx +36 -0
  378. package/src/team/NotificationMatrix/NotificationMatrix.tsx +144 -0
  379. package/src/team/NotificationMatrix/block.meta.ts +9 -0
  380. package/src/team/NotificationMatrix/fixtures.ts +43 -0
  381. package/src/team/PendingInvitations/PendingInvitations.test.tsx +30 -0
  382. package/src/team/PendingInvitations/PendingInvitations.tsx +132 -0
  383. package/src/team/PendingInvitations/block.meta.ts +9 -0
  384. package/src/team/PendingInvitations/fixtures.ts +39 -0
  385. package/src/team/ProfileHeader/ProfileHeader.test.tsx +29 -0
  386. package/src/team/ProfileHeader/ProfileHeader.tsx +106 -0
  387. package/src/team/ProfileHeader/block.meta.ts +10 -0
  388. package/src/team/ProfileHeader/fixtures.ts +28 -0
  389. package/src/team/SessionsDevices/SessionsDevices.test.tsx +30 -0
  390. package/src/team/SessionsDevices/SessionsDevices.tsx +126 -0
  391. package/src/team/SessionsDevices/block.meta.ts +10 -0
  392. package/src/team/SessionsDevices/fixtures.ts +58 -0
  393. package/src/team/index.ts +15 -0
  394. package/src/test/a11y.ts +18 -0
  395. package/src/test/heading-levels.test.tsx +69 -0
  396. package/src/test/setup.ts +36 -0
  397. package/README.md +0 -3
@@ -0,0 +1,120 @@
1
+ import { Check, Copy, Pencil } from '@gntik-ai/icons';
2
+ import {
3
+ Button,
4
+ Card,
5
+ CardAction,
6
+ CardDescription,
7
+ CardHeader,
8
+ CardTitle,
9
+ cn,
10
+ IconButton,
11
+ StatusTag,
12
+ } from '@gntik-ai/ui';
13
+ import { useEffect, useRef, useState, type ReactNode } from 'react';
14
+ import { DEPLOYMENT_DETAILS } from './fixtures';
15
+
16
+ export interface DescriptionItem {
17
+ id: string;
18
+ label: string;
19
+ /** Text value; also what the copy button copies unless `copyValue` is set. */
20
+ value?: string;
21
+ /** Rich value (overrides `value` for display). */
22
+ render?: ReactNode;
23
+ /** Shows the value as a StatusTag with this state key. */
24
+ status?: string;
25
+ /** Monospace value (IDs, regions, URLs). */
26
+ mono?: boolean;
27
+ /** Adds a copy button. */
28
+ copyable?: boolean;
29
+ copyValue?: string;
30
+ }
31
+
32
+ export interface DescriptionListCardProps {
33
+ title?: string;
34
+ description?: string;
35
+ items?: readonly DescriptionItem[];
36
+ /** Shows an Edit button in the header. */
37
+ onEdit?: () => void;
38
+ editLabel?: string;
39
+ /** Called after a value is copied. */
40
+ onCopy?: (item: DescriptionItem) => void;
41
+ /** Heading level of the title, to fit the page outline (default h3). */
42
+ titleAs?: 'h2' | 'h3' | 'h4';
43
+ className?: string;
44
+ }
45
+
46
+ function CopyButton({ item, onCopy }: { item: DescriptionItem; onCopy?: (item: DescriptionItem) => void }) {
47
+ const [copied, setCopied] = useState(false);
48
+ const timer = useRef<number | undefined>(undefined);
49
+ useEffect(() => () => window.clearTimeout(timer.current), []);
50
+ const copy = async () => {
51
+ const text = item.copyValue ?? item.value ?? '';
52
+ try {
53
+ await navigator.clipboard?.writeText(text);
54
+ } catch {}
55
+ setCopied(true);
56
+ onCopy?.(item);
57
+ window.clearTimeout(timer.current);
58
+ timer.current = window.setTimeout(() => setCopied(false), 1500);
59
+ };
60
+ return (
61
+ <>
62
+ <IconButton
63
+ icon={copied ? Check : Copy}
64
+ label={copied ? `${item.label} copied` : `Copy ${item.label}`}
65
+ size="sm"
66
+ variant="ghost"
67
+ className={cn('size-7 shrink-0', copied && 'text-success-text')}
68
+ onClick={copy}
69
+ />
70
+ <span className="sr-only" aria-live="polite">
71
+ {copied ? 'Copied to clipboard' : ''}
72
+ </span>
73
+ </>
74
+ );
75
+ }
76
+
77
+ /** Key–value detail card: label column, values (mono, status or rich), copy buttons and an Edit action. */
78
+ export function DescriptionListCard({
79
+ title = 'Configuration',
80
+ description = 'Settings of this deployment.',
81
+ items = DEPLOYMENT_DETAILS,
82
+ onEdit,
83
+ editLabel = 'Edit',
84
+ onCopy,
85
+ titleAs = 'h3',
86
+ className,
87
+ }: DescriptionListCardProps) {
88
+ return (
89
+ <Card className={className}>
90
+ <CardHeader divided>
91
+ <CardTitle as={titleAs}>{title}</CardTitle>
92
+ {description && <CardDescription>{description}</CardDescription>}
93
+ {onEdit && (
94
+ <CardAction>
95
+ <Button variant="secondary" size="sm" icon={Pencil} onClick={onEdit}>
96
+ {editLabel}
97
+ </Button>
98
+ </CardAction>
99
+ )}
100
+ </CardHeader>
101
+ <dl className="divide-y divide-border px-5">
102
+ {items.map((item) => (
103
+ <div key={item.id} className="grid grid-cols-1 gap-1 py-3.5 sm:grid-cols-[160px_minmax(0,1fr)] sm:gap-4">
104
+ <dt className="text-[13px] text-muted-foreground">{item.label}</dt>
105
+ <dd className="flex min-w-0 items-center gap-2 text-[13px] text-foreground">
106
+ {item.status ? (
107
+ <StatusTag status={item.status} />
108
+ ) : (
109
+ <span className={cn('min-w-0 truncate', item.mono && 'font-mono text-[12.5px]')}>
110
+ {item.render ?? item.value}
111
+ </span>
112
+ )}
113
+ {item.copyable && <CopyButton item={item} onCopy={onCopy} />}
114
+ </dd>
115
+ </div>
116
+ ))}
117
+ </dl>
118
+ </Card>
119
+ );
120
+ }
@@ -0,0 +1,9 @@
1
+ import type { BlockMeta } from '../../meta';
2
+
3
+ export const meta: BlockMeta = {
4
+ name: 'Description list card',
5
+ family: 'data-display',
6
+ status: 'beta',
7
+ description: 'Key–value detail card with mono, status and rich values, copy-to-clipboard buttons and an Edit action.',
8
+ uses: ['Card', 'Button', 'IconButton', 'StatusTag'],
9
+ };
@@ -0,0 +1,12 @@
1
+ import type { DescriptionItem } from './DescriptionListCard';
2
+
3
+ /** Configuration of a deployment. */
4
+ export const DEPLOYMENT_DETAILS: DescriptionItem[] = [
5
+ { id: 'status', label: 'Status', status: 'running' },
6
+ { id: 'id', label: 'Deployment ID', value: 'dep_7f3c9a21', mono: true, copyable: true },
7
+ { id: 'runtime', label: 'Runtime', value: 'node-24', mono: true },
8
+ { id: 'region', label: 'Region', value: 'eu-west-1', mono: true },
9
+ { id: 'endpoint', label: 'Endpoint', value: 'https://api.example.com/v1/orders', mono: true, copyable: true },
10
+ { id: 'owner', label: 'Owner', value: 'Platform team' },
11
+ { id: 'created', label: 'Created', value: 'Mar 12, 2026, 09:41' },
12
+ ];
@@ -0,0 +1,29 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import { expectNoAxeViolations } from '../../test/a11y';
3
+ import { KpiRow } from './KpiRow';
4
+
5
+ describe('KpiRow', () => {
6
+ it('renders the default KPIs as a description list with sparklines', async () => {
7
+ const { container } = render(<KpiRow />);
8
+ expect(screen.getByText('Active projects')).toBeInTheDocument();
9
+ expect(screen.getByText('$18,204')).toBeInTheDocument();
10
+ expect(screen.getAllByRole('img').length).toBe(4);
11
+ await expectNoAxeViolations(container);
12
+ });
13
+
14
+ it('colours the delta by sentiment, not direction', () => {
15
+ render(<KpiRow />);
16
+ const latency = screen.getByText(/Decreased by −8.1%/).parentElement;
17
+ expect(latency).toHaveAttribute('data-sentiment', 'positive');
18
+ const spend = screen.getByText(/Increased by \+4.2%/).parentElement;
19
+ expect(spend).toHaveAttribute('data-sentiment', 'negative');
20
+ expect(spend).toHaveClass('text-destructive-text');
21
+ });
22
+
23
+ it('renders the strip variant without notes or trends', async () => {
24
+ const { container } = render(<KpiRow variant="strip" showTrends={false} />);
25
+ expect(screen.queryByRole('img')).toBeNull();
26
+ expect(screen.queryByText('vs. last month')).toBeNull();
27
+ await expectNoAxeViolations(container);
28
+ });
29
+ });
@@ -0,0 +1,101 @@
1
+ import { cn, Sparkline, type SparklineTone } from '@gntik-ai/ui';
2
+ import { TrendDelta, type KpiDelta } from '../shared/TrendDelta';
3
+ import { KPI_ITEMS } from './fixtures';
4
+
5
+ export interface KpiItem {
6
+ id: string;
7
+ label: string;
8
+ /** Pre-formatted value ("38,921", "842 ms"). */
9
+ value: string;
10
+ delta?: KpiDelta;
11
+ /** Optional series for a sparkline under the value, oldest first. */
12
+ trend?: readonly number[];
13
+ trendTone?: SparklineTone;
14
+ }
15
+
16
+ export interface KpiRowProps {
17
+ /** 3–5 tiles read best; more wrap onto a new row. */
18
+ items?: readonly KpiItem[];
19
+ /** `cards` (one card per KPI, default) or `strip` (a single divided card, for page headers). */
20
+ variant?: 'cards' | 'strip';
21
+ /** Draw sparklines for items that have a `trend`. */
22
+ showTrends?: boolean;
23
+ /** Accessible name of the list. */
24
+ label?: string;
25
+ className?: string;
26
+ }
27
+
28
+ const COLS: Record<number, string> = {
29
+ 1: 'lg:grid-cols-1',
30
+ 2: 'lg:grid-cols-2',
31
+ 3: 'lg:grid-cols-3',
32
+ 4: 'lg:grid-cols-4',
33
+ 5: 'lg:grid-cols-5',
34
+ };
35
+
36
+ /**
37
+ * KPI row: label, big number, delta (direction and sentiment are independent) and an optional
38
+ * Sparkline. A description list, so each label is announced with its value.
39
+ */
40
+ export function KpiRow({
41
+ items = KPI_ITEMS,
42
+ variant = 'cards',
43
+ showTrends = true,
44
+ label = 'Key metrics',
45
+ className,
46
+ }: KpiRowProps) {
47
+ const strip = variant === 'strip';
48
+ const cols = COLS[Math.min(items.length, 5)] ?? 'lg:grid-cols-4';
49
+ return (
50
+ <dl
51
+ aria-label={label}
52
+ className={cn(
53
+ 'grid grid-cols-1 sm:grid-cols-2',
54
+ cols,
55
+ strip
56
+ ? 'divide-y divide-border overflow-hidden rounded-xl border border-border bg-card shadow-sm sm:divide-x lg:divide-y-0'
57
+ : 'gap-4',
58
+ className,
59
+ )}
60
+ >
61
+ {items.map((item) => (
62
+ <div
63
+ key={item.id}
64
+ className={cn(
65
+ 'flex min-w-0 flex-col',
66
+ strip ? 'px-5 py-4' : 'rounded-xl border border-border bg-card p-5 shadow-sm',
67
+ )}
68
+ >
69
+ <dt className={cn('truncate font-medium text-muted-foreground', strip ? 'text-[12px]' : 'text-[13px]')}>
70
+ {item.label}
71
+ </dt>
72
+ <dd className="mt-2 flex flex-wrap items-baseline gap-x-2 gap-y-1">
73
+ <span
74
+ className={cn(
75
+ 'font-semibold tracking-tight text-foreground tabular-nums',
76
+ strip ? 'text-[22px]' : 'text-[26px]',
77
+ )}
78
+ >
79
+ {item.value}
80
+ </span>
81
+ {item.delta && <TrendDelta {...item.delta} />}
82
+ </dd>
83
+ {item.delta?.note && !strip && <dd className="mt-1 text-[12px] text-muted-foreground">{item.delta.note}</dd>}
84
+ {showTrends && item.trend && item.trend.length > 1 && (
85
+ <dd className="mt-3">
86
+ <Sparkline
87
+ data={item.trend}
88
+ label={item.label}
89
+ area
90
+ tone={item.trendTone ?? 'primary'}
91
+ width={240}
92
+ height={40}
93
+ className="block h-auto w-full"
94
+ />
95
+ </dd>
96
+ )}
97
+ </div>
98
+ ))}
99
+ </dl>
100
+ );
101
+ }
@@ -0,0 +1,10 @@
1
+ import type { BlockMeta } from '../../meta';
2
+
3
+ export const meta: BlockMeta = {
4
+ name: 'KPI row',
5
+ family: 'data-display',
6
+ status: 'beta',
7
+ description:
8
+ 'Three to five stat tiles: label, value, a delta whose arrow and colour are independent, and an optional sparkline. Cards or a divided strip.',
9
+ uses: ['Sparkline'],
10
+ };
@@ -0,0 +1,34 @@
1
+ import type { KpiItem } from './KpiRow';
2
+
3
+ /** Four project KPIs: note latency and spend — same arrow logic, different sentiment. */
4
+ export const KPI_ITEMS: KpiItem[] = [
5
+ {
6
+ id: 'projects',
7
+ label: 'Active projects',
8
+ value: '142',
9
+ delta: { value: '+6', direction: 'up', sentiment: 'positive', note: 'vs. 30 days ago' },
10
+ trend: [128, 130, 131, 133, 134, 137, 139, 142],
11
+ },
12
+ {
13
+ id: 'requests',
14
+ label: 'Requests · 24h',
15
+ value: '38,921',
16
+ delta: { value: '+12.4%', direction: 'up', sentiment: 'positive', note: 'vs. previous 24h' },
17
+ trend: [31, 34, 33, 38, 36, 41, 39],
18
+ },
19
+ {
20
+ id: 'latency',
21
+ label: 'Avg latency',
22
+ value: '842 ms',
23
+ delta: { value: '−8.1%', direction: 'down', sentiment: 'positive', note: 'vs. previous 24h' },
24
+ trend: [910, 884, 902, 861, 852, 848, 842],
25
+ },
26
+ {
27
+ id: 'spend',
28
+ label: 'Spend · month to date',
29
+ value: '$18,204',
30
+ delta: { value: '+4.2%', direction: 'up', sentiment: 'negative', note: 'vs. last month' },
31
+ trend: [12.1, 13.4, 14.2, 15.0, 16.3, 17.1, 18.2],
32
+ trendTone: 'warning',
33
+ },
34
+ ];
@@ -0,0 +1,22 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import userEvent from '@testing-library/user-event';
3
+ import { expectNoAxeViolations } from '../../test/a11y';
4
+ import { ResourceCardGrid } from './ResourceCardGrid';
5
+
6
+ describe('ResourceCardGrid', () => {
7
+ it('renders one card per resource', async () => {
8
+ const { container } = render(<ResourceCardGrid />);
9
+ expect(screen.getByRole('list', { name: 'Resources' })).toBeInTheDocument();
10
+ expect(screen.getAllByRole('heading', { level: 3 })).toHaveLength(6);
11
+ expect(screen.getByText('Degraded')).toBeInTheDocument();
12
+ await expectNoAxeViolations(container);
13
+ });
14
+
15
+ it('runs a menu action for the card', async () => {
16
+ const onAction = vi.fn();
17
+ render(<ResourceCardGrid onAction={onAction} />);
18
+ await userEvent.click(screen.getByRole('button', { name: 'Actions for orders-db' }));
19
+ await userEvent.click(await screen.findByRole('menuitem', { name: 'Delete' }));
20
+ expect(onAction).toHaveBeenCalledWith('delete', expect.objectContaining({ id: 'db' }));
21
+ });
22
+ });
@@ -0,0 +1,98 @@
1
+ import { Copy, ExternalLink, Pencil, Trash2, type LucideIcon } from '@gntik-ai/icons';
2
+ import { Avatar, cn, MoreMenu, StatusTag, type MoreMenuAction } from '@gntik-ai/ui';
3
+ import { RESOURCE_CARDS } from './fixtures';
4
+
5
+ export interface ResourceCard {
6
+ id: string;
7
+ name: string;
8
+ description?: string;
9
+ /** Icon tile (a lucide icon). Ignored when `avatar` is set. */
10
+ icon?: LucideIcon;
11
+ /** Avatar instead of the icon tile (people, teams, organisations). */
12
+ avatar?: { name: string; src?: string };
13
+ /** StatusTag state key (running, paused, failed…). */
14
+ status?: string;
15
+ /** Two or three small figures at the bottom of the card. */
16
+ meta?: ReadonlyArray<{ label: string; value: string }>;
17
+ }
18
+
19
+ export type ResourceAction = 'open' | 'rename' | 'duplicate' | 'delete';
20
+
21
+ export interface ResourceCardGridProps {
22
+ items?: readonly ResourceCard[];
23
+ /** Accessible name of the list. */
24
+ label?: string;
25
+ /** Called with the default actions (Open, Rename, Duplicate, Delete). */
26
+ onAction?: (action: ResourceAction, item: ResourceCard) => void;
27
+ /** Replaces the default menu entirely. Return an empty array to hide the menu. */
28
+ getActions?: (item: ResourceCard) => Array<MoreMenuAction | 'separator'>;
29
+ /** Columns at the widest breakpoint. */
30
+ columns?: 2 | 3 | 4;
31
+ /** Heading level of each card's name, to fit the page outline (default h3). */
32
+ titleAs?: 'h2' | 'h3' | 'h4';
33
+ className?: string;
34
+ }
35
+
36
+ const COLS = { 2: 'lg:grid-cols-2', 3: 'lg:grid-cols-3', 4: 'lg:grid-cols-3 xl:grid-cols-4' } as const;
37
+
38
+ /** Responsive grid of resource cards: icon or avatar, name, status, meta figures and a row-actions MoreMenu. */
39
+ export function ResourceCardGrid({
40
+ items = RESOURCE_CARDS,
41
+ label = 'Resources',
42
+ onAction,
43
+ getActions,
44
+ columns = 3,
45
+ titleAs: TitleTag = 'h3',
46
+ className,
47
+ }: ResourceCardGridProps) {
48
+ const defaults = (item: ResourceCard): Array<MoreMenuAction | 'separator'> => [
49
+ { label: 'Open', icon: ExternalLink, onSelect: () => onAction?.('open', item) },
50
+ { label: 'Rename', icon: Pencil, onSelect: () => onAction?.('rename', item) },
51
+ { label: 'Duplicate', icon: Copy, onSelect: () => onAction?.('duplicate', item) },
52
+ 'separator',
53
+ { label: 'Delete', icon: Trash2, destructive: true, onSelect: () => onAction?.('delete', item) },
54
+ ];
55
+ return (
56
+ <ul aria-label={label} className={cn('grid grid-cols-1 gap-4 sm:grid-cols-2', COLS[columns], className)}>
57
+ {items.map((item) => {
58
+ const actions = getActions ? getActions(item) : defaults(item);
59
+ const IconCmp = item.icon;
60
+ return (
61
+ <li key={item.id} className="flex min-w-0 flex-col rounded-xl border border-border bg-card p-5 shadow-sm">
62
+ <div className="flex items-start justify-between gap-3">
63
+ {item.avatar ? (
64
+ <Avatar name={item.avatar.name} src={item.avatar.src} size="md" shape="rounded" />
65
+ ) : (
66
+ <span className="inline-flex size-10 shrink-0 items-center justify-center rounded-lg bg-secondary text-muted-foreground">
67
+ {IconCmp && <IconCmp size={20} aria-hidden />}
68
+ </span>
69
+ )}
70
+ <div className="flex items-center gap-2">
71
+ {item.status && <StatusTag status={item.status} />}
72
+ {actions.length > 0 && (
73
+ <MoreMenu items={actions} label={`Actions for ${item.name}`} variant="ghost" size="sm" />
74
+ )}
75
+ </div>
76
+ </div>
77
+ <div className="mt-4 min-w-0">
78
+ <TitleTag className="truncate text-[14px] font-semibold text-foreground">{item.name}</TitleTag>
79
+ {item.description && (
80
+ <p className="mt-0.5 truncate text-[12.5px] text-muted-foreground">{item.description}</p>
81
+ )}
82
+ </div>
83
+ {item.meta && item.meta.length > 0 && (
84
+ <dl className="mt-4 grid grid-cols-2 gap-3 border-t border-border pt-4">
85
+ {item.meta.map((m) => (
86
+ <div key={m.label} className="flex min-w-0 flex-col-reverse">
87
+ <dt className="truncate text-[11px] text-muted-foreground">{m.label}</dt>
88
+ <dd className="truncate font-mono text-[14px] text-foreground tabular-nums">{m.value}</dd>
89
+ </div>
90
+ ))}
91
+ </dl>
92
+ )}
93
+ </li>
94
+ );
95
+ })}
96
+ </ul>
97
+ );
98
+ }
@@ -0,0 +1,10 @@
1
+ import type { BlockMeta } from '../../meta';
2
+
3
+ export const meta: BlockMeta = {
4
+ name: 'Resource card grid',
5
+ family: 'data-display',
6
+ status: 'beta',
7
+ description:
8
+ 'Responsive grid of resource cards with icon or avatar, name, status, meta figures and a row-actions menu.',
9
+ uses: ['Avatar', 'StatusTag', 'MoreMenu'],
10
+ };
@@ -0,0 +1,72 @@
1
+ import { Boxes, Database, Globe, HardDrive, Server, Workflow } from '@gntik-ai/icons';
2
+ import type { ResourceCard } from './ResourceCardGrid';
3
+
4
+ /** Project resources. */
5
+ export const RESOURCE_CARDS: ResourceCard[] = [
6
+ {
7
+ id: 'api',
8
+ name: 'orders-api',
9
+ description: 'Public REST API',
10
+ icon: Server,
11
+ status: 'running',
12
+ meta: [
13
+ { label: 'Requests 30d', value: '1.2M' },
14
+ { label: 'Cost 30d', value: '$412' },
15
+ ],
16
+ },
17
+ {
18
+ id: 'db',
19
+ name: 'orders-db',
20
+ description: 'Postgres 17 · 3 replicas',
21
+ icon: Database,
22
+ status: 'running',
23
+ meta: [
24
+ { label: 'Storage', value: '84 GB' },
25
+ { label: 'Cost 30d', value: '$238' },
26
+ ],
27
+ },
28
+ {
29
+ id: 'worker',
30
+ name: 'invoice-worker',
31
+ description: 'Queue consumer',
32
+ icon: Workflow,
33
+ status: 'degraded',
34
+ meta: [
35
+ { label: 'Jobs 30d', value: '48,210' },
36
+ { label: 'Cost 30d', value: '$96' },
37
+ ],
38
+ },
39
+ {
40
+ id: 'cdn',
41
+ name: 'static-assets',
42
+ description: 'Edge cache',
43
+ icon: Globe,
44
+ status: 'running',
45
+ meta: [
46
+ { label: 'Hit rate', value: '97.4%' },
47
+ { label: 'Cost 30d', value: '$31' },
48
+ ],
49
+ },
50
+ {
51
+ id: 'batch',
52
+ name: 'nightly-export',
53
+ description: 'Scheduled job',
54
+ icon: Boxes,
55
+ status: 'paused',
56
+ meta: [
57
+ { label: 'Last run', value: '2 days ago' },
58
+ { label: 'Cost 30d', value: '$12' },
59
+ ],
60
+ },
61
+ {
62
+ id: 'bucket',
63
+ name: 'backups',
64
+ description: 'Object storage',
65
+ icon: HardDrive,
66
+ status: 'failed',
67
+ meta: [
68
+ { label: 'Objects', value: '18,422' },
69
+ { label: 'Cost 30d', value: '$54' },
70
+ ],
71
+ },
72
+ ];
@@ -0,0 +1,30 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import userEvent from '@testing-library/user-event';
3
+ import { expectNoAxeViolations } from '../../test/a11y';
4
+ import { StatCard } from './StatCard';
5
+
6
+ describe('StatCard', () => {
7
+ it('renders the first range by default', async () => {
8
+ const { container } = render(<StatCard />);
9
+ expect(screen.getByRole('heading', { name: 'Requests' })).toBeInTheDocument();
10
+ expect(screen.getByText('38,921')).toBeInTheDocument();
11
+ expect(screen.getByRole('img', { name: /Requests, 7d/ })).toBeInTheDocument();
12
+ await expectNoAxeViolations(container);
13
+ });
14
+
15
+ it('switches range and recalculates the value', async () => {
16
+ const onRangeChange = vi.fn();
17
+ render(<StatCard onRangeChange={onRangeChange} />);
18
+ await userEvent.click(screen.getByRole('button', { name: '90d' }));
19
+ expect(onRangeChange).toHaveBeenCalledWith('90d');
20
+ expect(screen.getByText('3.02M')).toBeInTheDocument();
21
+ expect(screen.getByRole('button', { name: '90d' })).toHaveAttribute('aria-pressed', 'true');
22
+ });
23
+
24
+ it('calls the footer action', async () => {
25
+ const onClick = vi.fn();
26
+ render(<StatCard action={{ label: 'Open report', onClick }} />);
27
+ await userEvent.click(screen.getByRole('button', { name: 'Open report' }));
28
+ expect(onClick).toHaveBeenCalled();
29
+ });
30
+ });