@gntik-ai/blocks 0.0.0-stage → 0.5.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 (385) hide show
  1. package/dist/index.d.ts +3494 -0
  2. package/dist/index.js +14911 -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.test.tsx +33 -0
  49. package/src/auth/SignUpForm/SignUpForm.tsx +154 -0
  50. package/src/auth/SignUpForm/block.meta.ts +9 -0
  51. package/src/auth/SsoButtons/SsoButtons.test.tsx +23 -0
  52. package/src/auth/SsoButtons/SsoButtons.tsx +52 -0
  53. package/src/auth/SsoButtons/block.meta.ts +9 -0
  54. package/src/auth/SsoButtons/fixtures.ts +15 -0
  55. package/src/auth/index.ts +15 -0
  56. package/src/auth/shared.tsx +113 -0
  57. package/src/billing/CostBreakdown/CostBreakdown.test.tsx +24 -0
  58. package/src/billing/CostBreakdown/CostBreakdown.tsx +95 -0
  59. package/src/billing/CostBreakdown/block.meta.ts +10 -0
  60. package/src/billing/CostBreakdown/fixtures.ts +9 -0
  61. package/src/billing/InvoiceTable/InvoiceTable.test.tsx +29 -0
  62. package/src/billing/InvoiceTable/InvoiceTable.tsx +130 -0
  63. package/src/billing/InvoiceTable/block.meta.ts +10 -0
  64. package/src/billing/InvoiceTable/fixtures.ts +38 -0
  65. package/src/billing/PaymentMethodCard/PaymentMethodCard.test.tsx +28 -0
  66. package/src/billing/PaymentMethodCard/PaymentMethodCard.tsx +103 -0
  67. package/src/billing/PaymentMethodCard/block.meta.ts +10 -0
  68. package/src/billing/PaymentMethodCard/fixtures.ts +12 -0
  69. package/src/billing/PlanCard/PlanCard.baseline.test.tsx +26 -0
  70. package/src/billing/PlanCard/PlanCard.summary.test.tsx +227 -0
  71. package/src/billing/PlanCard/PlanCard.test.tsx +27 -0
  72. package/src/billing/PlanCard/PlanCard.tsx +140 -0
  73. package/src/billing/PlanCard/PlanSummary.tsx +101 -0
  74. package/src/billing/PlanCard/README.md +23 -0
  75. package/src/billing/PlanCard/__snapshots__/PlanCard.baseline.test.tsx.snap +5 -0
  76. package/src/billing/PlanCard/block.meta.ts +60 -0
  77. package/src/billing/PlanCard/examples/SummaryActionsOnly.tsx +12 -0
  78. package/src/billing/PlanCard/examples/SummaryFeaturesOnly.tsx +14 -0
  79. package/src/billing/PlanCard/examples/SummaryFull.tsx +28 -0
  80. package/src/billing/PlanCard/examples/SummaryMinimal.tsx +5 -0
  81. package/src/billing/PlanCard/examples/SummaryPriceOnly.tsx +15 -0
  82. package/src/billing/PlanCard/examples/SummaryRenewalOnly.tsx +14 -0
  83. package/src/billing/PlanCard/examples/SummaryUnassigned.tsx +5 -0
  84. package/src/billing/PlanCard/examples/SummaryUnassignedNoLabel.tsx +5 -0
  85. package/src/billing/PlanCard/examples/SummaryZeroPrice.tsx +15 -0
  86. package/src/billing/PlanCard/fixtures.ts +19 -0
  87. package/src/billing/PlanDefinitionCard/PlanDefinitionCard.test.tsx +111 -0
  88. package/src/billing/PlanDefinitionCard/PlanDefinitionCard.tsx +86 -0
  89. package/src/billing/PlanDefinitionCard/README.md +29 -0
  90. package/src/billing/PlanDefinitionCard/block.meta.ts +46 -0
  91. package/src/billing/PlanDefinitionCard/examples/Compact.tsx +23 -0
  92. package/src/billing/PlanDefinitionCard/examples/Full.tsx +23 -0
  93. package/src/billing/PlanDefinitionCard/fixtures.ts +12 -0
  94. package/src/billing/QuotaMeters/QuotaMeters.test.tsx +23 -0
  95. package/src/billing/QuotaMeters/QuotaMeters.tsx +92 -0
  96. package/src/billing/QuotaMeters/block.meta.ts +10 -0
  97. package/src/billing/QuotaMeters/fixtures.ts +9 -0
  98. package/src/billing/SpendVsBudget/SpendVsBudget.test.tsx +24 -0
  99. package/src/billing/SpendVsBudget/SpendVsBudget.tsx +105 -0
  100. package/src/billing/SpendVsBudget/block.meta.ts +10 -0
  101. package/src/billing/SpendVsBudget/fixtures.ts +18 -0
  102. package/src/billing/format.ts +42 -0
  103. package/src/billing/index.ts +21 -0
  104. package/src/builders/CodePanel/CodePanel.test.tsx +280 -0
  105. package/src/builders/CodePanel/CodePanel.tsx +239 -0
  106. package/src/builders/CodePanel/block.meta.ts +10 -0
  107. package/src/builders/CodePanel/examples/controlled.tsx +15 -0
  108. package/src/builders/CodePanel/examples/diagnostics.tsx +40 -0
  109. package/src/builders/CodePanel/fixtures.ts +95 -0
  110. package/src/builders/CodePanel/marker-store.ts +90 -0
  111. package/src/builders/CodePanel/markers.ts +62 -0
  112. package/src/builders/FlowBuilder/FlowBuilder.test.tsx +480 -0
  113. package/src/builders/FlowBuilder/FlowBuilder.tsx +343 -0
  114. package/src/builders/FlowBuilder/block.meta.ts +20 -0
  115. package/src/builders/FlowBuilder/connections.ts +67 -0
  116. package/src/builders/FlowBuilder/decorated-nodes.ts +80 -0
  117. package/src/builders/FlowBuilder/examples/extensions.tsx +120 -0
  118. package/src/builders/FlowBuilder/extensions.ts +37 -0
  119. package/src/builders/FlowBuilder/fixtures.ts +80 -0
  120. package/src/builders/FlowBuilder/parts.tsx +196 -0
  121. package/src/builders/FlowBuilder/problems.tsx +40 -0
  122. package/src/builders/LogViewer/LogViewer.test.tsx +97 -0
  123. package/src/builders/LogViewer/LogViewer.tsx +253 -0
  124. package/src/builders/LogViewer/block.meta.ts +10 -0
  125. package/src/builders/LogViewer/fixtures.ts +49 -0
  126. package/src/builders/index.ts +15 -0
  127. package/src/data-display/ActivityFeed/ActivityFeed.test.tsx +62 -0
  128. package/src/data-display/ActivityFeed/ActivityFeed.tsx +118 -0
  129. package/src/data-display/ActivityFeed/block.meta.ts +10 -0
  130. package/src/data-display/ActivityFeed/fixtures.ts +29 -0
  131. package/src/data-display/ChartCard/ChartCard.ranges.test.tsx +282 -0
  132. package/src/data-display/ChartCard/ChartCard.test.tsx +54 -0
  133. package/src/data-display/ChartCard/ChartCard.tsx +218 -0
  134. package/src/data-display/ChartCard/block.meta.ts +10 -0
  135. package/src/data-display/ChartCard/examples/custom-ranges.tsx +37 -0
  136. package/src/data-display/ChartCard/examples/heatmap.tsx +20 -0
  137. package/src/data-display/ChartCard/examples/toolbar-owned-range.tsx +34 -0
  138. package/src/data-display/ChartCard/fixtures.ts +82 -0
  139. package/src/data-display/CommentThread/CommentThread.test.tsx +29 -0
  140. package/src/data-display/CommentThread/CommentThread.tsx +120 -0
  141. package/src/data-display/CommentThread/block.meta.ts +10 -0
  142. package/src/data-display/CommentThread/fixtures.ts +28 -0
  143. package/src/data-display/DescriptionListCard/DescriptionListCard.test.tsx +28 -0
  144. package/src/data-display/DescriptionListCard/DescriptionListCard.tsx +120 -0
  145. package/src/data-display/DescriptionListCard/block.meta.ts +9 -0
  146. package/src/data-display/DescriptionListCard/fixtures.ts +12 -0
  147. package/src/data-display/KpiRow/KpiRow.test.tsx +29 -0
  148. package/src/data-display/KpiRow/KpiRow.tsx +101 -0
  149. package/src/data-display/KpiRow/block.meta.ts +10 -0
  150. package/src/data-display/KpiRow/fixtures.ts +34 -0
  151. package/src/data-display/ResourceCardGrid/ResourceCardGrid.test.tsx +22 -0
  152. package/src/data-display/ResourceCardGrid/ResourceCardGrid.tsx +98 -0
  153. package/src/data-display/ResourceCardGrid/block.meta.ts +10 -0
  154. package/src/data-display/ResourceCardGrid/fixtures.ts +72 -0
  155. package/src/data-display/StatCard/StatCard.test.tsx +30 -0
  156. package/src/data-display/StatCard/StatCard.tsx +138 -0
  157. package/src/data-display/StatCard/block.meta.ts +10 -0
  158. package/src/data-display/StatCard/fixtures.ts +26 -0
  159. package/src/data-display/StatusTimeline/StatusTimeline.test.tsx +153 -0
  160. package/src/data-display/StatusTimeline/StatusTimeline.tsx +114 -0
  161. package/src/data-display/StatusTimeline/block.meta.ts +10 -0
  162. package/src/data-display/StatusTimeline/examples/Empty.tsx +6 -0
  163. package/src/data-display/StatusTimeline/fixtures.ts +62 -0
  164. package/src/data-display/index.ts +34 -0
  165. package/src/data-display/shared/TrendDelta.tsx +60 -0
  166. package/src/feedback/ConfirmDestructive/ConfirmDestructive.test.tsx +47 -0
  167. package/src/feedback/ConfirmDestructive/ConfirmDestructive.tsx +145 -0
  168. package/src/feedback/ConfirmDestructive/block.meta.ts +10 -0
  169. package/src/feedback/EmptyStates/EmptyStates.test.tsx +48 -0
  170. package/src/feedback/EmptyStates/EmptyStates.tsx +191 -0
  171. package/src/feedback/EmptyStates/block.meta.ts +10 -0
  172. package/src/feedback/EmptyStates/fixtures.ts +6 -0
  173. package/src/feedback/ErrorPanel/ErrorPanel.test.tsx +37 -0
  174. package/src/feedback/ErrorPanel/ErrorPanel.tsx +146 -0
  175. package/src/feedback/ErrorPanel/block.meta.ts +10 -0
  176. package/src/feedback/InlineCallout/InlineCallout.test.tsx +33 -0
  177. package/src/feedback/InlineCallout/InlineCallout.tsx +81 -0
  178. package/src/feedback/InlineCallout/block.meta.ts +9 -0
  179. package/src/feedback/Skeletons/Skeletons.test.tsx +25 -0
  180. package/src/feedback/Skeletons/Skeletons.tsx +142 -0
  181. package/src/feedback/Skeletons/block.meta.ts +10 -0
  182. package/src/feedback/SystemBanner/SystemBanner.test.tsx +29 -0
  183. package/src/feedback/SystemBanner/SystemBanner.tsx +105 -0
  184. package/src/feedback/SystemBanner/block.meta.ts +9 -0
  185. package/src/feedback/index.ts +14 -0
  186. package/src/feedback/types.ts +13 -0
  187. package/src/forms/CreateApiKeyDialog/CreateApiKeyDialog.test.tsx +36 -0
  188. package/src/forms/CreateApiKeyDialog/CreateApiKeyDialog.tsx +197 -0
  189. package/src/forms/CreateApiKeyDialog/block.meta.ts +10 -0
  190. package/src/forms/CreateApiKeyDialog/fixtures.ts +31 -0
  191. package/src/forms/CredentialDisclosureDialog/CredentialDisclosureDialog.test.tsx +339 -0
  192. package/src/forms/CredentialDisclosureDialog/CredentialDisclosureDialog.tsx +193 -0
  193. package/src/forms/CredentialDisclosureDialog/block.meta.ts +10 -0
  194. package/src/forms/CredentialDisclosureDialog/examples/Fresh.tsx +32 -0
  195. package/src/forms/CredentialDisclosureDialog/examples/ResponseGating.tsx +69 -0
  196. package/src/forms/CredentialDisclosureDialog/examples/Stored.tsx +32 -0
  197. package/src/forms/CredentialDisclosureDialog/fixtures.ts +14 -0
  198. package/src/forms/DangerZone/ConfirmDestructiveDialog.tsx +101 -0
  199. package/src/forms/DangerZone/DangerZone.test.tsx +28 -0
  200. package/src/forms/DangerZone/DangerZone.tsx +86 -0
  201. package/src/forms/DangerZone/block.meta.ts +10 -0
  202. package/src/forms/DangerZone/fixtures.ts +36 -0
  203. package/src/forms/DateRangeFilter/DateRangeFilter.test.tsx +26 -0
  204. package/src/forms/DateRangeFilter/DateRangeFilter.tsx +83 -0
  205. package/src/forms/DateRangeFilter/block.meta.ts +9 -0
  206. package/src/forms/DialogWizard/DialogWizard.test.tsx +95 -0
  207. package/src/forms/DialogWizard/DialogWizard.tsx +39 -0
  208. package/src/forms/DialogWizard/block.meta.ts +10 -0
  209. package/src/forms/DialogWizard/examples/CreateResource.tsx +65 -0
  210. package/src/forms/DialogWizard/fixtures.ts +20 -0
  211. package/src/forms/FileUploadPanel/FileUploadPanel.test.tsx +43 -0
  212. package/src/forms/FileUploadPanel/FileUploadPanel.tsx +244 -0
  213. package/src/forms/FileUploadPanel/block.meta.ts +10 -0
  214. package/src/forms/FileUploadPanel/fixtures.ts +66 -0
  215. package/src/forms/FormSection/FormSection.test.tsx +34 -0
  216. package/src/forms/FormSection/FormSection.tsx +77 -0
  217. package/src/forms/FormSection/block.meta.ts +9 -0
  218. package/src/forms/InviteMembersDialog/InviteMembersDialog.test.tsx +37 -0
  219. package/src/forms/InviteMembersDialog/InviteMembersDialog.tsx +183 -0
  220. package/src/forms/InviteMembersDialog/block.meta.ts +10 -0
  221. package/src/forms/InviteMembersDialog/fixtures.ts +14 -0
  222. package/src/forms/SettingsRow/SettingsRow.test.tsx +50 -0
  223. package/src/forms/SettingsRow/SettingsRow.tsx +74 -0
  224. package/src/forms/SettingsRow/block.meta.ts +9 -0
  225. package/src/forms/ValidationSummary/ValidationSummary.test.tsx +46 -0
  226. package/src/forms/ValidationSummary/ValidationSummary.tsx +75 -0
  227. package/src/forms/ValidationSummary/block.meta.ts +9 -0
  228. package/src/forms/ValidationSummary/fixtures.ts +13 -0
  229. package/src/forms/index.ts +27 -0
  230. package/src/index.ts +14 -0
  231. package/src/marketing/ComparisonTable/ComparisonTable.test.tsx +69 -0
  232. package/src/marketing/ComparisonTable/ComparisonTable.tsx +245 -0
  233. package/src/marketing/ComparisonTable/block.meta.ts +10 -0
  234. package/src/marketing/ComparisonTable/examples/options.tsx +48 -0
  235. package/src/marketing/ComparisonTable/fixtures.ts +83 -0
  236. package/src/marketing/Faq/Faq.test.tsx +25 -0
  237. package/src/marketing/Faq/Faq.tsx +68 -0
  238. package/src/marketing/Faq/block.meta.ts +10 -0
  239. package/src/marketing/Faq/fixtures.ts +32 -0
  240. package/src/marketing/FeatureGrid/FeatureGrid.test.tsx +19 -0
  241. package/src/marketing/FeatureGrid/FeatureGrid.tsx +79 -0
  242. package/src/marketing/FeatureGrid/block.meta.ts +10 -0
  243. package/src/marketing/FeatureGrid/fixtures.ts +36 -0
  244. package/src/marketing/Hero/Hero.test.tsx +33 -0
  245. package/src/marketing/Hero/Hero.tsx +107 -0
  246. package/src/marketing/Hero/block.meta.ts +10 -0
  247. package/src/marketing/PricingTable/PricingTable.test.tsx +92 -0
  248. package/src/marketing/PricingTable/PricingTable.tsx +323 -0
  249. package/src/marketing/PricingTable/block.meta.ts +10 -0
  250. package/src/marketing/PricingTable/fixtures.ts +34 -0
  251. package/src/marketing/actions.tsx +40 -0
  252. package/src/marketing/index.ts +15 -0
  253. package/src/meta.ts +23 -0
  254. package/src/page-chrome/BulkActionBar/BulkActionBar.test.tsx +34 -0
  255. package/src/page-chrome/BulkActionBar/BulkActionBar.tsx +76 -0
  256. package/src/page-chrome/BulkActionBar/block.meta.ts +9 -0
  257. package/src/page-chrome/PageHeader/PageHeader.test.tsx +68 -0
  258. package/src/page-chrome/PageHeader/PageHeader.tsx +152 -0
  259. package/src/page-chrome/PageHeader/block.meta.ts +10 -0
  260. package/src/page-chrome/PageHeader/fixtures.ts +29 -0
  261. package/src/page-chrome/PageToolbar/PageToolbar.test.tsx +38 -0
  262. package/src/page-chrome/PageToolbar/PageToolbar.tsx +138 -0
  263. package/src/page-chrome/PageToolbar/block.meta.ts +10 -0
  264. package/src/page-chrome/PageToolbar/fixtures.ts +8 -0
  265. package/src/page-chrome/SectionHeader/SectionHeader.test.tsx +28 -0
  266. package/src/page-chrome/SectionHeader/SectionHeader.tsx +73 -0
  267. package/src/page-chrome/SectionHeader/block.meta.ts +9 -0
  268. package/src/page-chrome/StickyActionBar/StickyActionBar.test.tsx +29 -0
  269. package/src/page-chrome/StickyActionBar/StickyActionBar.tsx +66 -0
  270. package/src/page-chrome/StickyActionBar/block.meta.ts +9 -0
  271. package/src/page-chrome/index.ts +13 -0
  272. package/src/page-chrome/types.ts +25 -0
  273. package/src/scheduling/AgendaList/AgendaList.test.tsx +28 -0
  274. package/src/scheduling/AgendaList/AgendaList.tsx +165 -0
  275. package/src/scheduling/AgendaList/block.meta.ts +10 -0
  276. package/src/scheduling/MonthCalendar/MonthCalendar.test.tsx +32 -0
  277. package/src/scheduling/MonthCalendar/MonthCalendar.tsx +223 -0
  278. package/src/scheduling/MonthCalendar/block.meta.ts +10 -0
  279. package/src/scheduling/TimeSlotPicker/TimeSlotPicker.test.tsx +26 -0
  280. package/src/scheduling/TimeSlotPicker/TimeSlotPicker.tsx +115 -0
  281. package/src/scheduling/TimeSlotPicker/block.meta.ts +10 -0
  282. package/src/scheduling/TimeSlotPicker/fixtures.ts +10 -0
  283. package/src/scheduling/WeekCalendar/WeekCalendar.test.tsx +27 -0
  284. package/src/scheduling/WeekCalendar/WeekCalendar.tsx +274 -0
  285. package/src/scheduling/WeekCalendar/block.meta.ts +10 -0
  286. package/src/scheduling/index.ts +11 -0
  287. package/src/scheduling/shared/CalendarHeader.tsx +54 -0
  288. package/src/scheduling/shared/EventTile.tsx +21 -0
  289. package/src/scheduling/shared/dates.ts +98 -0
  290. package/src/scheduling/shared/events.ts +90 -0
  291. package/src/scheduling/shared/fixtures.ts +54 -0
  292. package/src/shell/AppSidebar/AppSidebar.test.tsx +38 -0
  293. package/src/shell/AppSidebar/AppSidebar.tsx +226 -0
  294. package/src/shell/AppSidebar/block.meta.ts +10 -0
  295. package/src/shell/AppSidebar/fixtures.ts +58 -0
  296. package/src/shell/AppTopbar/AppTopbar.test.tsx +41 -0
  297. package/src/shell/AppTopbar/AppTopbar.tsx +82 -0
  298. package/src/shell/AppTopbar/block.meta.ts +19 -0
  299. package/src/shell/AppTopbar/fixtures.ts +22 -0
  300. package/src/shell/EnvironmentBadge/EnvironmentBadge.test.tsx +22 -0
  301. package/src/shell/EnvironmentBadge/EnvironmentBadge.tsx +55 -0
  302. package/src/shell/EnvironmentBadge/block.meta.ts +10 -0
  303. package/src/shell/GlobalSearch/GlobalSearch.test.tsx +28 -0
  304. package/src/shell/GlobalSearch/GlobalSearch.tsx +80 -0
  305. package/src/shell/GlobalSearch/block.meta.ts +10 -0
  306. package/src/shell/GlobalSearch/fixtures.ts +64 -0
  307. package/src/shell/index.ts +11 -0
  308. package/src/styles.css +2 -0
  309. package/src/tables/DataTable/DataTable.edit-views.test.tsx +249 -0
  310. package/src/tables/DataTable/DataTable.layout.test.tsx +188 -0
  311. package/src/tables/DataTable/DataTable.single-select.test.tsx +501 -0
  312. package/src/tables/DataTable/DataTable.test.tsx +268 -0
  313. package/src/tables/DataTable/DataTable.tsx +520 -0
  314. package/src/tables/DataTable/block.meta.ts +27 -0
  315. package/src/tables/DataTable/column-menu.tsx +109 -0
  316. package/src/tables/DataTable/column-sizing.ts +166 -0
  317. package/src/tables/DataTable/data-table-body.tsx +194 -0
  318. package/src/tables/DataTable/data-table-props.ts +156 -0
  319. package/src/tables/DataTable/data-table-row.tsx +220 -0
  320. package/src/tables/DataTable/data-table-utils.ts +195 -0
  321. package/src/tables/DataTable/density-toggle.tsx +30 -0
  322. package/src/tables/DataTable/editable-cell.tsx +274 -0
  323. package/src/tables/DataTable/examples/editable.tsx +71 -0
  324. package/src/tables/DataTable/examples/grouped.tsx +18 -0
  325. package/src/tables/DataTable/examples/resizable-pinned.tsx +20 -0
  326. package/src/tables/DataTable/examples/saved-views.tsx +61 -0
  327. package/src/tables/DataTable/examples/single-selection.tsx +55 -0
  328. package/src/tables/DataTable/examples/virtualized.tsx +20 -0
  329. package/src/tables/DataTable/fixtures.ts +119 -0
  330. package/src/tables/DataTable/header-cell.tsx +86 -0
  331. package/src/tables/DataTable/resize-handle.tsx +88 -0
  332. package/src/tables/DataTable/single-selection.tsx +149 -0
  333. package/src/tables/DataTable/use-table-views.ts +79 -0
  334. package/src/tables/DataTable/use-virtual-rows.ts +83 -0
  335. package/src/tables/DataTable/views.tsx +186 -0
  336. package/src/tables/FilterBar/FilterBar.test.tsx +43 -0
  337. package/src/tables/FilterBar/FilterBar.tsx +199 -0
  338. package/src/tables/FilterBar/block.meta.ts +10 -0
  339. package/src/tables/FilterBar/fixtures.ts +32 -0
  340. package/src/tables/InlineEditRow/InlineEditRow.test.tsx +41 -0
  341. package/src/tables/InlineEditRow/InlineEditRow.tsx +215 -0
  342. package/src/tables/InlineEditRow/block.meta.ts +10 -0
  343. package/src/tables/InlineEditRow/fixtures.ts +28 -0
  344. package/src/tables/PaginationFooter/PaginationFooter.test.tsx +29 -0
  345. package/src/tables/PaginationFooter/PaginationFooter.tsx +94 -0
  346. package/src/tables/PaginationFooter/block.meta.ts +10 -0
  347. package/src/tables/SubordinateList/SubordinateList.test.tsx +351 -0
  348. package/src/tables/SubordinateList/SubordinateList.tsx +140 -0
  349. package/src/tables/SubordinateList/block.meta.ts +10 -0
  350. package/src/tables/SubordinateList/examples/ResourceForm.tsx +129 -0
  351. package/src/tables/SubordinateList/examples/empty.tsx +5 -0
  352. package/src/tables/SubordinateList/examples/error.tsx +5 -0
  353. package/src/tables/SubordinateList/examples/loading.tsx +5 -0
  354. package/src/tables/TruncatedCollectionFooter/TruncatedCollectionFooter.test.tsx +159 -0
  355. package/src/tables/TruncatedCollectionFooter/TruncatedCollectionFooter.tsx +60 -0
  356. package/src/tables/TruncatedCollectionFooter/block.meta.ts +10 -0
  357. package/src/tables/TruncatedCollectionFooter/examples/exact.tsx +6 -0
  358. package/src/tables/TruncatedCollectionFooter/examples/has-more.tsx +12 -0
  359. package/src/tables/TruncatedCollectionFooter/fixtures.ts +2 -0
  360. package/src/tables/index.ts +45 -0
  361. package/src/team/MembersTable/MembersTable.test.tsx +29 -0
  362. package/src/team/MembersTable/MembersTable.tsx +141 -0
  363. package/src/team/MembersTable/block.meta.ts +9 -0
  364. package/src/team/MembersTable/fixtures.ts +19 -0
  365. package/src/team/NotificationMatrix/NotificationMatrix.test.tsx +36 -0
  366. package/src/team/NotificationMatrix/NotificationMatrix.tsx +144 -0
  367. package/src/team/NotificationMatrix/block.meta.ts +9 -0
  368. package/src/team/NotificationMatrix/fixtures.ts +43 -0
  369. package/src/team/PendingInvitations/PendingInvitations.test.tsx +30 -0
  370. package/src/team/PendingInvitations/PendingInvitations.tsx +132 -0
  371. package/src/team/PendingInvitations/block.meta.ts +9 -0
  372. package/src/team/PendingInvitations/fixtures.ts +39 -0
  373. package/src/team/ProfileHeader/ProfileHeader.test.tsx +29 -0
  374. package/src/team/ProfileHeader/ProfileHeader.tsx +106 -0
  375. package/src/team/ProfileHeader/block.meta.ts +10 -0
  376. package/src/team/ProfileHeader/fixtures.ts +28 -0
  377. package/src/team/SessionsDevices/SessionsDevices.test.tsx +30 -0
  378. package/src/team/SessionsDevices/SessionsDevices.tsx +126 -0
  379. package/src/team/SessionsDevices/block.meta.ts +10 -0
  380. package/src/team/SessionsDevices/fixtures.ts +58 -0
  381. package/src/team/index.ts +15 -0
  382. package/src/test/a11y.ts +18 -0
  383. package/src/test/heading-levels.test.tsx +69 -0
  384. package/src/test/setup.ts +36 -0
  385. package/README.md +0 -3
@@ -0,0 +1,28 @@
1
+ import { Button } from '@gntik-ai/ui';
2
+ import { PlanCard } from '../PlanCard';
3
+
4
+ export default function SummaryFull() {
5
+ return (
6
+ <PlanCard
7
+ variant="summary"
8
+ plan={{
9
+ name: 'Fixture Plan',
10
+ statusLabel: 'Active',
11
+ priceMonthly: 10,
12
+ priceAnnual: 100,
13
+ currency: 'EUR',
14
+ cycle: 'monthly',
15
+ renewsOn: '2027-01-01',
16
+ features: ['Feature A', 'Feature B'],
17
+ }}
18
+ actions={
19
+ <>
20
+ <Button onClick={() => window.alert('Upgrade')}>Upgrade</Button>
21
+ <Button variant="secondary" onClick={() => window.alert('Manage')}>
22
+ Manage
23
+ </Button>
24
+ </>
25
+ }
26
+ />
27
+ );
28
+ }
@@ -0,0 +1,5 @@
1
+ import { PlanCard } from '../PlanCard';
2
+
3
+ export default function SummaryMinimal() {
4
+ return <PlanCard variant="summary" plan={{ name: 'Fixture Plan', statusLabel: 'Active' }} />;
5
+ }
@@ -0,0 +1,15 @@
1
+ import { PlanCard } from '../PlanCard';
2
+
3
+ export default function SummaryPriceOnly() {
4
+ return (
5
+ <PlanCard
6
+ variant="summary"
7
+ plan={{
8
+ name: 'Fixture Plan',
9
+ statusLabel: 'Active',
10
+ priceMonthly: 10,
11
+ currency: 'EUR',
12
+ }}
13
+ />
14
+ );
15
+ }
@@ -0,0 +1,14 @@
1
+ import { PlanCard } from '../PlanCard';
2
+
3
+ export default function SummaryRenewalOnly() {
4
+ return (
5
+ <PlanCard
6
+ variant="summary"
7
+ plan={{
8
+ name: 'Fixture Plan',
9
+ statusLabel: 'Active',
10
+ renewsOn: '2027-01-01',
11
+ }}
12
+ />
13
+ );
14
+ }
@@ -0,0 +1,5 @@
1
+ import { PlanCard } from '../PlanCard';
2
+
3
+ export default function SummaryUnassigned() {
4
+ return <PlanCard variant="summary" emptyLabel="No plan assigned" />;
5
+ }
@@ -0,0 +1,5 @@
1
+ import { PlanCard } from '../PlanCard';
2
+
3
+ export default function SummaryUnassignedNoLabel() {
4
+ return <PlanCard variant="summary" />;
5
+ }
@@ -0,0 +1,15 @@
1
+ import { PlanCard } from '../PlanCard';
2
+
3
+ export default function SummaryZeroPrice() {
4
+ return (
5
+ <PlanCard
6
+ variant="summary"
7
+ plan={{
8
+ name: 'Fixture Plan',
9
+ statusLabel: 'Active',
10
+ priceMonthly: 0,
11
+ currency: 'EUR',
12
+ }}
13
+ />
14
+ );
15
+ }
@@ -0,0 +1,19 @@
1
+ import type { BillingPlan } from './PlanCard';
2
+
3
+ /** Sample current plan (neutral workspace data). */
4
+ export const samplePlan: BillingPlan = {
5
+ name: 'Scale',
6
+ status: 'active',
7
+ priceMonthly: 1200,
8
+ priceAnnual: 1000,
9
+ currency: 'USD',
10
+ renewsOn: '2026-06-01',
11
+ summary: '50 seats · 2 regions',
12
+ features: [
13
+ '500,000 requests / month',
14
+ '120M tokens / month',
15
+ '20 GB storage',
16
+ 'Audit log and SSO',
17
+ 'Email support · 1 business day',
18
+ ],
19
+ };
@@ -0,0 +1,111 @@
1
+ import { render, screen, within } from '@testing-library/react';
2
+ import userEvent from '@testing-library/user-event';
3
+ import { Button } from '@gntik-ai/ui';
4
+ import { expectNoAxeViolations } from '../../test/a11y';
5
+ import { PlanDefinitionCard } from './PlanDefinitionCard';
6
+
7
+ const definition = {
8
+ name: 'Fixture Plan',
9
+ slug: 'fixture-plan',
10
+ description: 'A plan definition.',
11
+ status: 'active' as const,
12
+ capabilities: { Search: true, Export: false },
13
+ quotas: { Requests: 100, Storage: '20 GB' },
14
+ tenantCount: 4,
15
+ updatedAt: '2027-01-01',
16
+ };
17
+
18
+ describe('PlanDefinitionCard', () => {
19
+ it('keeps consumer buttons and links native, keyboard reachable and actionable', async () => {
20
+ const user = userEvent.setup();
21
+ const onEdit = vi.fn();
22
+ const onView = vi.fn();
23
+ render(
24
+ <PlanDefinitionCard
25
+ plan={definition}
26
+ actions={
27
+ <>
28
+ <Button onClick={onEdit}>Edit</Button>
29
+ <a href="#tenants" onClick={onView} className="text-primary-text underline">
30
+ View tenants
31
+ </a>
32
+ </>
33
+ }
34
+ />,
35
+ );
36
+ const edit = screen.getByRole('button', { name: 'Edit' });
37
+ const view = screen.getByRole('link', { name: 'View tenants' });
38
+ expect(edit.tagName).toBe('BUTTON');
39
+ expect(view.tagName).toBe('A');
40
+ await user.tab();
41
+ expect(edit).toHaveFocus();
42
+ await user.keyboard('{Enter}');
43
+ expect(onEdit).toHaveBeenCalledTimes(1);
44
+ await user.keyboard(' ');
45
+ expect(onEdit).toHaveBeenCalledTimes(2);
46
+ await user.tab();
47
+ expect(view).toHaveFocus();
48
+ await user.keyboard('{Enter}');
49
+ expect(onView).toHaveBeenCalledTimes(1);
50
+ });
51
+
52
+ it('omits the optional description and preserves zero quota and tenant values', () => {
53
+ render(
54
+ <PlanDefinitionCard
55
+ plan={{
56
+ ...definition,
57
+ description: undefined,
58
+ tenantCount: 0,
59
+ quotas: { Requests: 0 },
60
+ }}
61
+ />,
62
+ );
63
+ expect(screen.queryByText('A plan definition.')).toBeNull();
64
+ expect(screen.getByText('Requests', { selector: 'dt' }).nextElementSibling).toHaveTextContent('0');
65
+ expect(screen.getByText('Assigned tenants', { selector: 'dt' }).nextElementSibling).toHaveTextContent('0');
66
+ });
67
+
68
+ it.each(['dark', 'light', 'high_contrast'])('has no axe violations in %s at either density', async (theme) => {
69
+ document.documentElement.className = theme;
70
+ for (const density of ['compact', 'full'] as const) {
71
+ const { container, unmount } = render(
72
+ <PlanDefinitionCard plan={definition} density={density} actions={<Button>Edit</Button>} />,
73
+ );
74
+ await expectNoAxeViolations(container);
75
+ unmount();
76
+ }
77
+ });
78
+ it.each([
79
+ ['draft', 'Draft'],
80
+ ['active', 'Active'],
81
+ ['deprecated', 'Deprecated'],
82
+ ['archived', 'Archived'],
83
+ ] as const)('shows a visible %s label and allows consumer override', (status, label) => {
84
+ const { unmount } = render(<PlanDefinitionCard plan={{ ...definition, status }} />);
85
+ expect(screen.getByText(label)).toBeVisible();
86
+ unmount();
87
+ render(<PlanDefinitionCard plan={{ ...definition, status }} statusLabels={{ [status]: 'Consumer status' }} />);
88
+ expect(screen.getByText('Consumer status')).toBeVisible();
89
+ expect(screen.queryByText(label)).toBeNull();
90
+ });
91
+ it.each(['compact', 'full'] as const)('renders the definition in %s density without commercial nodes', (density) => {
92
+ const { container } = render(<PlanDefinitionCard plan={definition} density={density} />);
93
+ expect(screen.getByRole('heading', { name: 'Fixture Plan' })).toBeInTheDocument();
94
+ expect(screen.getByText('fixture-plan')).toBeInTheDocument();
95
+ expect(screen.getByText('A plan definition.')).toBeInTheDocument();
96
+ expect(screen.getByText('Active')).toBeInTheDocument();
97
+ const flags = screen.getByRole('list', { name: 'Capabilities' });
98
+ expect(
99
+ within(flags)
100
+ .getAllByRole('listitem')
101
+ .map((item) => item.textContent),
102
+ ).toEqual(['SearchEnabled', 'ExportDisabled']);
103
+ expect(screen.getByText('Requests', { selector: 'dt' }).nextElementSibling).toHaveTextContent('100');
104
+ expect(screen.getByText('Storage', { selector: 'dt' }).nextElementSibling).toHaveTextContent('20 GB');
105
+ expect(screen.getByText('Assigned tenants', { selector: 'dt' }).nextElementSibling).toHaveTextContent('4');
106
+ expect(screen.getByText('Updated', { selector: 'dt' }).nextElementSibling).toHaveTextContent('January 1, 2027');
107
+ expect(screen.queryByRole('group', { name: 'Billing cycle' })).toBeNull();
108
+ expect(screen.queryByRole('button')).toBeNull();
109
+ expect(container.textContent).not.toMatch(/€|\$|price|Billed|Renews|Upgrade|Monthly|Annual/);
110
+ });
111
+ });
@@ -0,0 +1,86 @@
1
+ import type { ReactNode } from 'react';
2
+ import { Badge, Card, cn, type BadgeTone } from '@gntik-ai/ui';
3
+ import { formatDate, type DateInput } from '../format';
4
+ import { samplePlanDefinition } from './fixtures';
5
+
6
+ export type PlanDefinitionStatus = 'draft' | 'active' | 'deprecated' | 'archived';
7
+
8
+ export interface PlanDefinition {
9
+ name: string;
10
+ slug: string;
11
+ description?: string;
12
+ status: PlanDefinitionStatus;
13
+ capabilities?: Record<string, boolean>;
14
+ quotas?: Record<string, string | number>;
15
+ tenantCount: number;
16
+ updatedAt: DateInput;
17
+ }
18
+
19
+ export interface PlanDefinitionCardProps {
20
+ plan?: PlanDefinition;
21
+ density?: 'compact' | 'full';
22
+ /** Consumer-supplied kit Buttons or native links; no billing actions. */
23
+ actions?: ReactNode;
24
+ statusLabels?: Partial<Record<PlanDefinitionStatus, string>>;
25
+ titleAs?: 'h2' | 'h3' | 'h4';
26
+ className?: string;
27
+ }
28
+
29
+ const STATUS: Record<PlanDefinitionStatus, { label: string; tone: BadgeTone }> = {
30
+ draft: { label: 'Draft', tone: 'info' },
31
+ active: { label: 'Active', tone: 'success' },
32
+ deprecated: { label: 'Deprecated', tone: 'warning' },
33
+ archived: { label: 'Archived', tone: 'neutral' },
34
+ };
35
+
36
+ /** Administrative plan information, with no commercial fields or actions. */
37
+ export function PlanDefinitionCard({
38
+ plan = samplePlanDefinition,
39
+ density = 'full',
40
+ actions,
41
+ statusLabels,
42
+ titleAs: TitleTag = 'h3',
43
+ className,
44
+ }: PlanDefinitionCardProps) {
45
+ const status = STATUS[plan.status];
46
+ return (
47
+ <Card className={cn(density === 'compact' ? 'p-4' : 'p-6', className)}>
48
+ <div className="flex flex-wrap items-center gap-2">
49
+ <TitleTag className="text-lg font-semibold text-foreground">{plan.name}</TitleTag>
50
+ <Badge tone={status.tone} dot>
51
+ {statusLabels?.[plan.status] ?? status.label}
52
+ </Badge>
53
+ </div>
54
+ <p className="mt-1 font-mono text-sm text-muted-foreground">{plan.slug}</p>
55
+ {plan.description && <p className="mt-2 text-sm text-foreground">{plan.description}</p>}
56
+ {plan.capabilities && Object.keys(plan.capabilities).length > 0 && (
57
+ <ul aria-label="Capabilities" className={cn('mt-4 grid gap-2 text-sm', density === 'full' && 'sm:grid-cols-2')}>
58
+ {Object.entries(plan.capabilities).map(([name, enabled]) => (
59
+ <li key={name} className="flex flex-wrap items-center justify-between gap-2">
60
+ <span className="text-foreground">{name}</span>
61
+ <span className="text-muted-foreground">{enabled ? 'Enabled' : 'Disabled'}</span>
62
+ </li>
63
+ ))}
64
+ </ul>
65
+ )}
66
+ <dl className={cn('mt-4 grid gap-3 text-sm', density === 'full' && 'sm:grid-cols-2')}>
67
+ {plan.quotas &&
68
+ Object.entries(plan.quotas).map(([key, value]) => (
69
+ <div key={key}>
70
+ <dt className="text-muted-foreground">{key}</dt>
71
+ <dd className="font-medium text-foreground">{value}</dd>
72
+ </div>
73
+ ))}
74
+ <div>
75
+ <dt className="text-muted-foreground">Assigned tenants</dt>
76
+ <dd className="font-medium text-foreground tabular-nums">{plan.tenantCount}</dd>
77
+ </div>
78
+ <div>
79
+ <dt className="text-muted-foreground">Updated</dt>
80
+ <dd className="text-foreground">{formatDate(plan.updatedAt)}</dd>
81
+ </div>
82
+ </dl>
83
+ {actions && <div className="mt-4 flex flex-wrap items-center gap-2">{actions}</div>}
84
+ </Card>
85
+ );
86
+ }
@@ -0,0 +1,29 @@
1
+ # Plan definition card
2
+
3
+ Import `PlanDefinitionCard`, `PlanDefinition`, `PlanDefinitionStatus`, `PlanDefinitionCardProps` and `planDefinitionCardMeta` from `@gntik-ai/blocks`. This administrative presentation has no price, cycle, renewal or built-in upgrade action.
4
+
5
+ | Prop | Contract |
6
+ | ------------------------ | ---------------------------------------------------------------------------------------------------- |
7
+ | `plan` | `PlanDefinition`; omitted uses the neutral catalogue fixture |
8
+ | `plan.name`, `plan.slug` | Required display name and identifier |
9
+ | `plan.description` | Optional description; absent means no description node |
10
+ | `plan.status` | Required draft/active/deprecated/archived; each has a distinct English label |
11
+ | `statusLabels` | Optional partial status-to-label mapping supplied by the consumer |
12
+ | `plan.capabilities` | Optional `Record<string, boolean>`; labelled list entries show Enabled or Disabled |
13
+ | `plan.quotas` | Optional `Record<string, string \| number>` rendered as key/value pairs in a `dl`; zero is preserved |
14
+ | `plan.tenantCount` | Required assigned tenant count, including zero |
15
+ | `plan.updatedAt` | Required Date or ISO string, formatted through billing `formatDate` |
16
+ | `density` | compact/full (default full); both show all supplied data |
17
+ | `actions` | Optional ReactNode slot of kit Buttons or native links |
18
+ | `titleAs` | h2/h3/h4, default h3 |
19
+ | `className` | Optional token-backed classes |
20
+
21
+ ARIA: heading at the requested level; lifecycle badge uses visible text, never colour alone; capabilities are a named list; quotas, tenant count and update date use a description list. Actions retain their native button/link semantics. Dark, light and high_contrast axe coverage includes both densities and browser contrast checks.
22
+
23
+ | Key | Behavior |
24
+ | --------------- | -------------------------------- |
25
+ | Tab / Shift+Tab | Focus consumer buttons and links |
26
+ | Enter | Activate focused button or link |
27
+ | Space | Activate focused button |
28
+
29
+ Preview IDs: `plan-definition-card--compact` and `plan-definition-card--full`. The examples show an Edit button and View tenants link. Consumers supply domain actions and override lifecycle labels as needed.
@@ -0,0 +1,46 @@
1
+ import type { BlockMeta } from '../../meta';
2
+
3
+ /** Catalogue contract read by get_block and llms-full.txt. */
4
+ export const documentation = {
5
+ pattern:
6
+ 'Heading (titleAs h2/h3/h4), visible lifecycle Badge, Capabilities list with Enabled/Disabled text, quota/tenant/update description list, and consumer native actions. No price, billing cycle, renewal or built-in upgrade action. Axe coverage in dark, light and high_contrast at compact and full densities.',
7
+ keyboard: [
8
+ ['Tab / Shift+Tab', 'Move between consumer-supplied buttons and links.'],
9
+ ['Enter', 'Activate a focused button or link.'],
10
+ ['Space', 'Activate a focused button.'],
11
+ ],
12
+ tokens: ['card', 'foreground', 'muted-foreground', 'border', 'focus-ring'],
13
+ props: [
14
+ [
15
+ 'plan',
16
+ 'PlanDefinition: name, slug, status, tenantCount and updatedAt required; optional description, capabilities (Record<string, boolean>) and quotas (Record<string, string | number>). Omitted plan uses the neutral definition fixture.',
17
+ ],
18
+ ['density', 'compact | full (default); both show all supplied definition data.'],
19
+ [
20
+ 'statusLabels',
21
+ 'Partial<Record<draft | active | deprecated | archived, string>> overrides English lifecycle labels.',
22
+ ],
23
+ ['actions', 'ReactNode slot of kit Buttons or native links; absent by default.'],
24
+ ['titleAs / className', 'h2 | h3 (default) | h4; additional token-backed classes.'],
25
+ ],
26
+ examples: ['compact', 'full'],
27
+ };
28
+
29
+ export const meta: BlockMeta = {
30
+ name: 'Plan definition card',
31
+ family: 'billing',
32
+ status: 'beta',
33
+ description: [
34
+ 'Administrative plan definition without billing fields: name, slug, optional description, labelled draft/active/deprecated/archived status, enabled/disabled capabilities, quotas, assigned tenants and formatted update date. Compact and full densities, consumer action slots and statusLabels overrides. See README.md for props, ARIA, keyboard and examples.',
35
+ '',
36
+ '| Prop | Contract |',
37
+ '| --- | --- |',
38
+ ...documentation.props.map(([prop, contract]) => `| ${prop} | ${contract?.replaceAll('|', '\\|')} |`),
39
+ '',
40
+ 'Examples:',
41
+ ...documentation.examples.map(
42
+ (example) => `- [${example}](/preview.html?kind=block&id=plan-definition-card--${example})`,
43
+ ),
44
+ ].join('\n'),
45
+ uses: ['Card', 'Badge', 'Button'],
46
+ };
@@ -0,0 +1,23 @@
1
+ import { Button } from '@gntik-ai/ui';
2
+ import { PlanDefinitionCard } from '../PlanDefinitionCard';
3
+ import { samplePlanDefinition } from '../fixtures';
4
+
5
+ export default function Compact() {
6
+ return (
7
+ <PlanDefinitionCard
8
+ plan={samplePlanDefinition}
9
+ density="compact"
10
+ actions={
11
+ <>
12
+ <Button onClick={() => window.alert('Edit')}>Edit</Button>
13
+ <a
14
+ href="#tenants"
15
+ className="text-primary-text underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus-ring"
16
+ >
17
+ View tenants
18
+ </a>
19
+ </>
20
+ }
21
+ />
22
+ );
23
+ }
@@ -0,0 +1,23 @@
1
+ import { Button } from '@gntik-ai/ui';
2
+ import { PlanDefinitionCard } from '../PlanDefinitionCard';
3
+ import { samplePlanDefinition } from '../fixtures';
4
+
5
+ export default function Full() {
6
+ return (
7
+ <PlanDefinitionCard
8
+ plan={samplePlanDefinition}
9
+ density="full"
10
+ actions={
11
+ <>
12
+ <Button onClick={() => window.alert('Edit')}>Edit</Button>
13
+ <a
14
+ href="#tenants"
15
+ className="text-primary-text underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus-ring"
16
+ >
17
+ View tenants
18
+ </a>
19
+ </>
20
+ }
21
+ />
22
+ );
23
+ }
@@ -0,0 +1,12 @@
1
+ import type { PlanDefinition } from './PlanDefinitionCard';
2
+
3
+ export const samplePlanDefinition: PlanDefinition = {
4
+ name: 'Workspace',
5
+ slug: 'workspace',
6
+ description: 'Shared capabilities and quotas for a workspace.',
7
+ status: 'active',
8
+ capabilities: { Search: true, Export: false },
9
+ quotas: { Requests: 10000, Storage: '20 GB' },
10
+ tenantCount: 4,
11
+ updatedAt: '2027-01-01',
12
+ };
@@ -0,0 +1,23 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import userEvent from '@testing-library/user-event';
3
+ import { expectNoAxeViolations } from '../../test/a11y';
4
+ import { QuotaMeters } from './QuotaMeters';
5
+
6
+ describe('QuotaMeters', () => {
7
+ it('renders one meter per quota with used/limit labels', async () => {
8
+ const { container } = render(<QuotaMeters />);
9
+ expect(screen.getAllByRole('meter')).toHaveLength(4);
10
+ expect(screen.getByText('412,800 / 500,000 requests')).toBeInTheDocument();
11
+ expect(screen.getByText('78.4M / 120M tokens')).toBeInTheDocument();
12
+ await expectNoAxeViolations(container);
13
+ });
14
+
15
+ it('switches to the projection and flags the overage', async () => {
16
+ const user = userEvent.setup();
17
+ render(<QuotaMeters />);
18
+ await user.click(screen.getByRole('button', { name: 'Projected' }));
19
+ expect(screen.getByText('521,000 / 500,000 requests')).toBeInTheDocument();
20
+ expect(screen.getByText('21,000 requests over the plan limit')).toBeInTheDocument();
21
+ expect(screen.getByRole('meter', { name: 'Requests' })).toHaveAttribute('data-level', 'critical');
22
+ });
23
+ });
@@ -0,0 +1,92 @@
1
+ import { useState, type ReactNode } from 'react';
2
+ import { Meter, Toggle, ToggleGroup, cn, type MeterThresholds } from '@gntik-ai/ui';
3
+ import { formatQuantity } from '../format';
4
+ import { sampleQuotas } from './fixtures';
5
+
6
+ export interface Quota {
7
+ id: string;
8
+ label: string;
9
+ used: number;
10
+ limit: number;
11
+ /** End-of-cycle projection. When any quota has one, a Current/Projected toggle appears. */
12
+ projected?: number;
13
+ /** Unit after the numbers, e.g. "requests" or "GB". */
14
+ unit?: string;
15
+ /** Helper line under the bar. Over the limit, a default overage note is shown. */
16
+ note?: ReactNode;
17
+ }
18
+
19
+ export type QuotaScope = 'current' | 'projected';
20
+
21
+ export interface QuotaMetersProps {
22
+ quotas?: Quota[];
23
+ /** Heading above the meters. */
24
+ title?: string;
25
+ /** Percent-of-limit thresholds for amber and red (default 80 / 100). */
26
+ thresholds?: MeterThresholds;
27
+ /** Formats used/limit numbers. Default: grouped, compact above a million. */
28
+ formatValue?: (value: number) => string;
29
+ defaultScope?: QuotaScope;
30
+ onScopeChange?: (scope: QuotaScope) => void;
31
+ /** Heading level of the title, to fit the page outline (default h3). */
32
+ titleAs?: 'h2' | 'h3' | 'h4';
33
+ className?: string;
34
+ }
35
+
36
+ /** Plan quotas as meters: green with headroom, amber near the cap, red over it. */
37
+ export function QuotaMeters({
38
+ quotas = sampleQuotas,
39
+ title = 'Plan usage',
40
+ thresholds,
41
+ formatValue = formatQuantity,
42
+ defaultScope = 'current',
43
+ onScopeChange,
44
+ titleAs: TitleTag = 'h3',
45
+ className,
46
+ }: QuotaMetersProps) {
47
+ const [scope, setScope] = useState<QuotaScope>(defaultScope);
48
+ const canProject = quotas.some((q) => q.projected != null);
49
+ const showProjected = canProject && scope === 'projected';
50
+
51
+ const changeScope = (value: string[]) => {
52
+ const next = value[0];
53
+ if (next !== 'current' && next !== 'projected') return;
54
+ setScope(next);
55
+ onScopeChange?.(next);
56
+ };
57
+
58
+ return (
59
+ <section aria-label={title} className={cn('grid gap-5', className)}>
60
+ <div className="flex flex-wrap items-center justify-between gap-3">
61
+ <TitleTag className="text-[13px] font-medium text-muted-foreground">{title}</TitleTag>
62
+ {canProject && (
63
+ <ToggleGroup size="sm" aria-label="Usage period" value={[scope]} onValueChange={changeScope}>
64
+ <Toggle value="current">Current cycle</Toggle>
65
+ <Toggle value="projected">Projected</Toggle>
66
+ </ToggleGroup>
67
+ )}
68
+ </div>
69
+ <div className="grid grid-cols-1 gap-x-8 gap-y-6 sm:grid-cols-2">
70
+ {quotas.map((q) => {
71
+ const value = showProjected ? (q.projected ?? q.used) : q.used;
72
+ const unit = q.unit ? ` ${q.unit}` : '';
73
+ const text = `${formatValue(value)} / ${formatValue(q.limit)}${unit}`;
74
+ const over = value - q.limit;
75
+ const note = q.note ?? (over > 0 ? `${formatValue(over)}${unit} over the plan limit` : undefined);
76
+ return (
77
+ <Meter
78
+ key={q.id}
79
+ label={q.label}
80
+ value={value}
81
+ max={q.limit}
82
+ thresholds={thresholds}
83
+ valueLabel={text}
84
+ aria-valuetext={text}
85
+ note={note}
86
+ />
87
+ );
88
+ })}
89
+ </div>
90
+ </section>
91
+ );
92
+ }
@@ -0,0 +1,10 @@
1
+ import type { BlockMeta } from '../../meta';
2
+
3
+ export const meta: BlockMeta = {
4
+ name: 'Quota meters',
5
+ family: 'billing',
6
+ status: 'beta',
7
+ description:
8
+ 'Plan quotas as usage meters with used/limit labels, amber/red thresholds, overage notes and a current/projected toggle.',
9
+ uses: ['Meter', 'ToggleGroup', 'Toggle'],
10
+ };
@@ -0,0 +1,9 @@
1
+ import type { Quota } from './QuotaMeters';
2
+
3
+ /** Sample plan quotas: usage so far this cycle and the end-of-cycle projection. */
4
+ export const sampleQuotas: Quota[] = [
5
+ { id: 'requests', label: 'Requests', used: 412_800, limit: 500_000, projected: 521_000, unit: 'requests' },
6
+ { id: 'tokens', label: 'Tokens', used: 78_400_000, limit: 120_000_000, projected: 99_100_000, unit: 'tokens' },
7
+ { id: 'storage', label: 'Storage', used: 14.2, limit: 20, projected: 17.1, unit: 'GB' },
8
+ { id: 'seats', label: 'Seats', used: 38, limit: 50, projected: 38, unit: 'seats' },
9
+ ];
@@ -0,0 +1,24 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import userEvent from '@testing-library/user-event';
3
+ import { expectNoAxeViolations } from '../../test/a11y';
4
+ import { SpendVsBudget } from './SpendVsBudget';
5
+
6
+ describe('SpendVsBudget', () => {
7
+ it('renders the summary and the chart figure', async () => {
8
+ const { container } = render(<SpendVsBudget />);
9
+ expect(screen.getByRole('heading', { name: 'Spend vs budget' })).toBeInTheDocument();
10
+ expect(screen.getByText('Within budget')).toBeInTheDocument();
11
+ expect(screen.getByRole('figure', { name: /spent, .* forecast against a \$2,000.00 budget/ })).toBeInTheDocument();
12
+ await expectNoAxeViolations(container);
13
+ });
14
+
15
+ it('flags a forecast over budget and toggles series from the legend', async () => {
16
+ const user = userEvent.setup();
17
+ render(<SpendVsBudget budget={1500} />);
18
+ expect(screen.getByText('Forecast over budget')).toBeInTheDocument();
19
+ const budget = screen.getByRole('button', { name: 'Budget' });
20
+ expect(budget).toHaveAttribute('aria-pressed', 'true');
21
+ await user.click(budget);
22
+ expect(budget).toHaveAttribute('aria-pressed', 'false');
23
+ });
24
+ });