@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,138 @@
1
+ import { Activity, ArrowRight, type LucideIcon } from '@gntik-ai/icons';
2
+ import { Button, Card, cn, Link, Sparkline, Toggle, ToggleGroup, type SparklineTone } from '@gntik-ai/ui';
3
+ import { useState } from 'react';
4
+ import { TrendDelta, type KpiDelta } from '../shared/TrendDelta';
5
+ import { STAT_RANGES } from './fixtures';
6
+
7
+ export interface StatCardRange {
8
+ /** Range key, e.g. "7d". */
9
+ value: string;
10
+ /** Toggle label. */
11
+ label: string;
12
+ /** Pre-formatted headline value for this range. */
13
+ stat: string;
14
+ delta?: KpiDelta;
15
+ /** Series drawn under the value, oldest first. */
16
+ series: readonly number[];
17
+ }
18
+
19
+ export interface StatCardProps {
20
+ label?: string;
21
+ /** Leading icon tile (a lucide icon). */
22
+ icon?: LucideIcon;
23
+ /** One entry per range; with a single entry the range toggle is hidden. */
24
+ ranges?: readonly StatCardRange[];
25
+ /** Initially selected range (uncontrolled). */
26
+ defaultRange?: string;
27
+ /** Selected range (controlled). */
28
+ range?: string;
29
+ onRangeChange?: (range: string) => void;
30
+ /** `area` line (default) or daily `bar` shape. */
31
+ chart?: 'area' | 'bar';
32
+ tone?: SparklineTone;
33
+ /** Footer link-style action ("View requests →"). */
34
+ action?: { label: string; onClick?: () => void; href?: string };
35
+ /** Heading level of the title, to fit the page outline (default h3). */
36
+ titleAs?: 'h2' | 'h3' | 'h4';
37
+ className?: string;
38
+ }
39
+
40
+ /** A single rich stat: icon tile, label, value, delta, a range toggle and a trend chart, with a footer action. */
41
+ export function StatCard({
42
+ label = 'Requests',
43
+ icon: IconCmp = Activity,
44
+ ranges = STAT_RANGES,
45
+ defaultRange,
46
+ range: rangeProp,
47
+ onRangeChange,
48
+ chart = 'area',
49
+ tone = 'primary',
50
+ action = { label: 'View requests' },
51
+ titleAs: TitleTag = 'h3',
52
+ className,
53
+ }: StatCardProps) {
54
+ const [inner, setInner] = useState(defaultRange ?? ranges[0]?.value ?? '');
55
+ const current = rangeProp ?? inner;
56
+ const active = ranges.find((r) => r.value === current) ?? ranges[0];
57
+ const select = (next: string) => {
58
+ setInner(next);
59
+ onRangeChange?.(next);
60
+ };
61
+ return (
62
+ <Card className={cn('flex flex-col', className)}>
63
+ <div className="flex flex-1 flex-col p-5">
64
+ <div className="flex items-start justify-between gap-3">
65
+ <div className="flex min-w-0 items-center gap-3">
66
+ <span className="inline-flex size-9 shrink-0 items-center justify-center rounded-md bg-primary/14 text-primary-chip-text">
67
+ <IconCmp size={18} aria-hidden />
68
+ </span>
69
+ <TitleTag className="truncate text-[13px] font-medium text-muted-foreground">{label}</TitleTag>
70
+ </div>
71
+ {ranges.length > 1 && (
72
+ <ToggleGroup
73
+ aria-label={`${label} range`}
74
+ size="sm"
75
+ value={[current]}
76
+ onValueChange={(v) => {
77
+ const next = v[0];
78
+ if (next) select(next);
79
+ }}
80
+ >
81
+ {ranges.map((r) => (
82
+ <Toggle key={r.value} value={r.value} className="font-mono">
83
+ {r.label}
84
+ </Toggle>
85
+ ))}
86
+ </ToggleGroup>
87
+ )}
88
+ </div>
89
+ {active && (
90
+ <>
91
+ <p className="mt-3 flex flex-wrap items-baseline gap-x-2 gap-y-1" aria-live="polite">
92
+ <span className="text-[26px] font-semibold tracking-tight text-foreground tabular-nums">
93
+ {active.stat}
94
+ </span>
95
+ {active.delta && <TrendDelta {...active.delta} />}
96
+ </p>
97
+ {active.delta?.note && <p className="mt-1 text-[12px] text-muted-foreground">{active.delta.note}</p>}
98
+ <Sparkline
99
+ data={active.series}
100
+ label={`${label}, ${active.label}`}
101
+ variant={chart === 'bar' ? 'bar' : 'line'}
102
+ area={chart === 'area'}
103
+ markers={chart === 'area' ? ['last'] : []}
104
+ tone={tone}
105
+ width={280}
106
+ height={56}
107
+ className="mt-4 block h-auto w-full"
108
+ />
109
+ </>
110
+ )}
111
+ </div>
112
+ {action && (
113
+ <div className="border-t border-border px-5 py-3">
114
+ {action.href ? (
115
+ <Link
116
+ underline="hover"
117
+ href={action.href}
118
+ className="inline-flex items-center gap-1 text-[12.5px] font-semibold"
119
+ >
120
+ {action.label}
121
+ <ArrowRight size={14} aria-hidden className="rtl:-scale-x-100" />
122
+ </Link>
123
+ ) : (
124
+ <Button
125
+ variant="ghost"
126
+ size="sm"
127
+ trailingIcon={ArrowRight}
128
+ className="-mx-2 text-primary-text hover:text-primary-text"
129
+ onClick={action.onClick}
130
+ >
131
+ {action.label}
132
+ </Button>
133
+ )}
134
+ </div>
135
+ )}
136
+ </Card>
137
+ );
138
+ }
@@ -0,0 +1,10 @@
1
+ import type { BlockMeta } from '../../meta';
2
+
3
+ export const meta: BlockMeta = {
4
+ name: 'Stat card',
5
+ family: 'data-display',
6
+ status: 'beta',
7
+ description:
8
+ 'One rich stat: icon tile, value and delta that recalculate per range (7d/30d/90d), a trend chart and a footer action.',
9
+ uses: ['Card', 'ToggleGroup', 'Sparkline', 'Button'],
10
+ };
@@ -0,0 +1,26 @@
1
+ import type { StatCardRange } from './StatCard';
2
+
3
+ /** Requests per range: the value and delta recalculate when the range changes. */
4
+ export const STAT_RANGES: StatCardRange[] = [
5
+ {
6
+ value: '7d',
7
+ label: '7d',
8
+ stat: '38,921',
9
+ delta: { value: '+12.4%', direction: 'up', sentiment: 'positive', note: 'vs. previous 7 days' },
10
+ series: [31, 34, 33, 38, 36, 41, 39],
11
+ },
12
+ {
13
+ value: '30d',
14
+ label: '30d',
15
+ stat: '1.09M',
16
+ delta: { value: '+9.6%', direction: 'up', sentiment: 'positive', note: 'vs. previous 30 days' },
17
+ series: [28, 30, 29, 33, 31, 30, 34, 32, 36, 35, 38, 37],
18
+ },
19
+ {
20
+ value: '90d',
21
+ label: '90d',
22
+ stat: '3.02M',
23
+ delta: { value: '+22.8%', direction: 'up', sentiment: 'positive', note: 'vs. previous 90 days' },
24
+ series: [22, 24, 23, 26, 25, 28, 27, 30, 29, 33, 32, 36],
25
+ },
26
+ ];
@@ -0,0 +1,153 @@
1
+ import { render, screen, within } from '@testing-library/react';
2
+ import userEvent from '@testing-library/user-event';
3
+ import { I18nProvider } from '@gntik-ai/ui';
4
+ import { expectNoAxeViolations } from '../../test/a11y';
5
+ import { StatusTimeline, type StatusEvent } from './StatusTimeline';
6
+ import { DEPLOYMENT_EVENTS } from './fixtures';
7
+
8
+ describe('StatusTimeline', () => {
9
+ it.each(['', 'dark', 'high_contrast'])('has an accessible empty and populated state in theme "%s"', async (theme) => {
10
+ const { container, rerender } = render(
11
+ <div className={theme}>
12
+ <StatusTimeline events={[]} />
13
+ </div>,
14
+ );
15
+ await expectNoAxeViolations(container);
16
+ rerender(
17
+ <div className={theme}>
18
+ <StatusTimeline maxVisible={3} />
19
+ </div>,
20
+ );
21
+ await expectNoAxeViolations(container);
22
+ await userEvent.click(screen.getByRole('button', { name: 'Show 3 earlier events' }));
23
+ await expectNoAxeViolations(container);
24
+ });
25
+
26
+ it('localizes default toggle and actor labels while retaining partial message overrides', async () => {
27
+ render(
28
+ <I18nProvider locale="es" messages={{ 'statusTimeline.label': 'Historial' }}>
29
+ <StatusTimeline maxVisible={5} />
30
+ </I18nProvider>,
31
+ );
32
+ expect(screen.getByRole('list', { name: 'Historial' })).toBeInTheDocument();
33
+ expect(screen.getByText('por System')).toBeInTheDocument();
34
+ await userEvent.click(screen.getByRole('button', { name: 'Ver 1 cambio anterior' }));
35
+ expect(screen.getByRole('button', { name: 'Ver menos' })).toHaveAttribute('aria-expanded', 'true');
36
+ });
37
+
38
+ it('keeps the singular English toggle default', () => {
39
+ render(<StatusTimeline maxVisible={5} />);
40
+ expect(screen.getByRole('button', { name: 'Show 1 earlier event' })).toBeInTheDocument();
41
+ });
42
+
43
+ it('uses consumer labels for empty, populated, collapsed and expanded timelines', async () => {
44
+ const events: StatusEvent[] = [
45
+ {
46
+ id: '1',
47
+ status: 'Activo',
48
+ tone: 'success',
49
+ title: 'Servicio iniciado',
50
+ description: 'Sistema listo.',
51
+ actor: 'Ana',
52
+ at: '2026-01-03T12:00:00Z',
53
+ },
54
+ {
55
+ id: '2',
56
+ status: 'Preparando',
57
+ tone: 'info',
58
+ title: 'Configuración aplicada',
59
+ actor: 'Luis',
60
+ at: '2026-01-02T12:00:00Z',
61
+ },
62
+ {
63
+ id: '3',
64
+ status: 'Creado',
65
+ tone: 'neutral',
66
+ title: 'Servicio creado',
67
+ actor: 'Sistema',
68
+ at: '2026-01-01T12:00:00Z',
69
+ },
70
+ ];
71
+ const labels = {
72
+ label: 'Historial',
73
+ emptyLabel: 'Sin historial de estado',
74
+ showFewerLabel: 'Ver menos',
75
+ showEarlierLabel: (hidden: number) => `Ver ${hidden} cambios anteriores`,
76
+ actorLabel: 'por',
77
+ };
78
+ const { container, rerender } = render(<StatusTimeline {...labels} events={[]} maxVisible={1} />);
79
+ const expectLocalized = () => {
80
+ for (const text of ['Status history', 'Show fewer', 'Show', 'earlier', 'event', 'by ']) {
81
+ expect(container).not.toHaveTextContent(text);
82
+ expect(container.querySelector('[aria-label]')?.getAttribute('aria-label')).not.toContain(text);
83
+ }
84
+ };
85
+ expectLocalized();
86
+ rerender(<StatusTimeline {...labels} events={events} timeFormat="absolute" />);
87
+ expect(screen.getAllByRole('listitem')).toHaveLength(3);
88
+ expect(screen.getByText('por Ana')).toBeInTheDocument();
89
+ expect(screen.queryByRole('button')).not.toBeInTheDocument();
90
+ expectLocalized();
91
+ rerender(<StatusTimeline {...labels} events={events} maxVisible={1} timeFormat="absolute" />);
92
+ expect(screen.getAllByRole('listitem')).toHaveLength(1);
93
+ expectLocalized();
94
+ await userEvent.click(screen.getByRole('button', { name: 'Ver 2 cambios anteriores' }));
95
+ expect(screen.getAllByRole('listitem')).toHaveLength(3);
96
+ expectLocalized();
97
+ await userEvent.click(screen.getByRole('button', { name: 'Ver menos' }));
98
+ expect(screen.getAllByRole('listitem')).toHaveLength(1);
99
+ expectLocalized();
100
+ });
101
+
102
+ it('uses the provider locale for the empty state and English without a provider', () => {
103
+ const { rerender } = render(
104
+ <I18nProvider locale="es">
105
+ <StatusTimeline events={[]} />
106
+ </I18nProvider>,
107
+ );
108
+ expect(screen.getByRole('region', { name: 'Historial de estado' })).toHaveTextContent('Sin historial de estado');
109
+ rerender(<StatusTimeline events={[]} />);
110
+ expect(screen.getByRole('region', { name: 'Status history' })).toHaveTextContent('No status history');
111
+ });
112
+
113
+ it('shows an accessible empty state without events, sample copy or a toggle', () => {
114
+ const { container } = render(
115
+ <StatusTimeline events={[]} label="Historial" emptyLabel="Sin historial de estado" maxVisible={2} />,
116
+ );
117
+ expect(screen.getAllByText('Sin historial de estado')).toHaveLength(1);
118
+ const region = screen.getByRole('region', { name: 'Historial' });
119
+ expect(
120
+ within(region).getAllByRole('heading', {
121
+ name: 'Sin historial de estado',
122
+ }),
123
+ ).toHaveLength(1);
124
+ expect(region).toHaveTextContent(/^Sin historial de estado$/);
125
+ expect(screen.queryByRole('listitem')).not.toBeInTheDocument();
126
+ expect(screen.queryByRole('button')).not.toBeInTheDocument();
127
+ expect(container.querySelector('time')).toBeNull();
128
+ expect(container.querySelector('[data-tone]')).toBeNull();
129
+ for (const event of DEPLOYMENT_EVENTS) {
130
+ for (const text of [event.status, event.title, event.description, event.actor]) {
131
+ if (text) expect(container).not.toHaveTextContent(text);
132
+ }
133
+ }
134
+ });
135
+
136
+ it('renders every event as an ordered list item with a time element', async () => {
137
+ const { container } = render(<StatusTimeline />);
138
+ const list = screen.getByRole('list', { name: 'Status history' });
139
+ expect(within(list).getAllByRole('listitem')).toHaveLength(6);
140
+ expect(container.querySelectorAll('time')).toHaveLength(6);
141
+ await expectNoAxeViolations(container);
142
+ });
143
+
144
+ it('collapses older events behind a toggle', async () => {
145
+ render(<StatusTimeline maxVisible={3} />);
146
+ expect(screen.getAllByRole('listitem')).toHaveLength(3);
147
+ const toggle = screen.getByRole('button', { name: 'Show 3 earlier events' });
148
+ expect(toggle).toHaveAttribute('aria-expanded', 'false');
149
+ await userEvent.click(toggle);
150
+ expect(screen.getAllByRole('listitem')).toHaveLength(6);
151
+ expect(screen.getByRole('button', { name: 'Show fewer' })).toHaveAttribute('aria-expanded', 'true');
152
+ });
153
+ });
@@ -0,0 +1,114 @@
1
+ import { Button, EmptyState, StatusDot, Timestamp, useI18n, type DateInput, type StatusDotTone } from '@gntik-ai/ui';
2
+ import { useState } from 'react';
3
+ import { DEPLOYMENT_EVENTS } from './fixtures';
4
+
5
+ export interface StatusEvent {
6
+ id: string;
7
+ /** Short state name next to the dot ("Running"). */
8
+ status: string;
9
+ tone: StatusDotTone;
10
+ title: string;
11
+ description?: string;
12
+ at: DateInput;
13
+ /** Who or what caused the change. */
14
+ actor?: string;
15
+ /** Pulses the dot (the current, in-progress state). */
16
+ live?: boolean;
17
+ }
18
+
19
+ export interface StatusTimelineProps {
20
+ /** Events, newest first. */
21
+ events?: readonly StatusEvent[];
22
+ /** Accessible name of the list. */
23
+ label?: string;
24
+ /** Title shown when events is an explicit empty array. */
25
+ emptyLabel?: string;
26
+ /** Collapse toggle text. */
27
+ showFewerLabel?: string;
28
+ /** Expand toggle text, given the number of hidden events. */
29
+ showEarlierLabel?: (hidden: number) => string;
30
+ /** Prefix before the actor's name. */
31
+ actorLabel?: string;
32
+ /** Events shown before "Show earlier"; all when omitted. */
33
+ maxVisible?: number;
34
+ /** `relative` ("3 hours ago") or `absolute` timestamps. */
35
+ timeFormat?: 'relative' | 'absolute';
36
+ className?: string;
37
+ }
38
+
39
+ /** Vertical timeline of state changes, or a named empty state for an explicit empty events array. */
40
+ export function StatusTimeline({
41
+ events = DEPLOYMENT_EVENTS,
42
+ label: labelProp,
43
+ emptyLabel: emptyLabelProp,
44
+ showFewerLabel: showFewerLabelProp,
45
+ showEarlierLabel: showEarlierLabelProp,
46
+ actorLabel: actorLabelProp,
47
+ maxVisible,
48
+ timeFormat = 'relative',
49
+ className,
50
+ }: StatusTimelineProps) {
51
+ const { t } = useI18n();
52
+ const label = labelProp ?? t('statusTimeline.label');
53
+ const emptyLabel = emptyLabelProp ?? t('statusTimeline.empty');
54
+ const showFewerLabel = showFewerLabelProp ?? t('statusTimeline.showFewer');
55
+ const showEarlierLabel = showEarlierLabelProp ?? ((count: number) => t('statusTimeline.showEarlier', { count }));
56
+ const actorLabel = actorLabelProp ?? t('statusTimeline.actor');
57
+ const [expanded, setExpanded] = useState(false);
58
+ if (events.length === 0) {
59
+ return <EmptyState role="region" aria-label={label} size="sm" title={emptyLabel} className={className} />;
60
+ }
61
+ const limit = !expanded && maxVisible !== undefined ? maxVisible : events.length;
62
+ const shown = events.slice(0, limit);
63
+ const hidden = events.length - shown.length;
64
+ return (
65
+ <div className={className}>
66
+ <ol aria-label={label} className="flex flex-col">
67
+ {shown.map((event, i) => {
68
+ const last = i === shown.length - 1;
69
+ return (
70
+ <li key={event.id} className="relative flex gap-3 pb-6 last:pb-0">
71
+ {!last && <span aria-hidden className="absolute top-5 bottom-0 start-[9px] w-px bg-border" />}
72
+ <span className="relative z-10 mt-1 flex size-5 shrink-0 items-center justify-center rounded-full bg-card">
73
+ <StatusDot tone={event.tone} pulse={event.live} size="lg" />
74
+ </span>
75
+ <div className="min-w-0 flex-1">
76
+ <div className="flex flex-col gap-0.5 sm:flex-row sm:items-baseline sm:justify-between sm:gap-4">
77
+ <p className="text-[13px] text-foreground">
78
+ <span className="font-semibold">{event.status}</span>
79
+ <span className="text-muted-foreground"> · </span>
80
+ {event.title}
81
+ </p>
82
+ <Timestamp value={event.at} format={timeFormat} className="shrink-0 font-mono text-[11px]" />
83
+ </div>
84
+ {(event.description || event.actor) && (
85
+ <p className="mt-0.5 text-[12.5px] text-muted-foreground">
86
+ {event.description}
87
+ {event.description && event.actor && ' '}
88
+ {event.actor && (
89
+ <span>
90
+ {`${actorLabel} `}
91
+ {event.actor}
92
+ </span>
93
+ )}
94
+ </p>
95
+ )}
96
+ </div>
97
+ </li>
98
+ );
99
+ })}
100
+ </ol>
101
+ {(hidden > 0 || (expanded && maxVisible !== undefined && events.length > maxVisible)) && (
102
+ <Button
103
+ variant="ghost"
104
+ size="sm"
105
+ className="mt-3 ms-6"
106
+ aria-expanded={expanded}
107
+ onClick={() => setExpanded((v) => !v)}
108
+ >
109
+ {expanded ? showFewerLabel : showEarlierLabel(hidden)}
110
+ </Button>
111
+ )}
112
+ </div>
113
+ );
114
+ }
@@ -0,0 +1,10 @@
1
+ import type { BlockMeta } from '../../meta';
2
+
3
+ export const meta: BlockMeta = {
4
+ name: 'Status timeline',
5
+ family: 'data-display',
6
+ status: 'beta',
7
+ description:
8
+ 'Vertical timeline of state changes with a StatusDot per state, details, actor and Timestamp; collapses older events and renders an accessible empty state for an explicit empty events array, with a localizable emptyLabel. The label, showFewerLabel, showEarlierLabel(hidden) and actorLabel props override the localized defaults.',
9
+ uses: ['StatusDot', 'Timestamp', 'Button', 'EmptyState'],
10
+ };
@@ -0,0 +1,6 @@
1
+ import { StatusTimeline } from '../StatusTimeline';
2
+
3
+ /** /preview.html?kind=block&id=status-timeline--empty */
4
+ export default function Empty() {
5
+ return <StatusTimeline events={[]} emptyLabel="No status changes yet" />;
6
+ }
@@ -0,0 +1,62 @@
1
+ import type { StatusEvent } from './StatusTimeline';
2
+
3
+ const HOUR = 3_600_000;
4
+ /** Fixtures are relative to load time so relative timestamps stay fresh in the docs. */
5
+ export const FIXTURE_NOW = Date.now();
6
+
7
+ /** State changes of a deployment, newest first. */
8
+ export const DEPLOYMENT_EVENTS: StatusEvent[] = [
9
+ {
10
+ id: 'e6',
11
+ status: 'Running',
12
+ tone: 'success',
13
+ title: 'Deployment healthy',
14
+ description: 'All 3 replicas passed health checks.',
15
+ at: FIXTURE_NOW - 0.2 * HOUR,
16
+ actor: 'System',
17
+ live: true,
18
+ },
19
+ {
20
+ id: 'e5',
21
+ status: 'Deploying',
22
+ tone: 'info',
23
+ title: 'Rolling out v2.14.0',
24
+ description: 'Canary at 10%, then 50%, then 100%.',
25
+ at: FIXTURE_NOW - 0.6 * HOUR,
26
+ actor: 'Ana Lopez',
27
+ },
28
+ {
29
+ id: 'e4',
30
+ status: 'Approved',
31
+ tone: 'primary',
32
+ title: 'Release approved',
33
+ at: FIXTURE_NOW - 1.5 * HOUR,
34
+ actor: 'Sam Carter',
35
+ },
36
+ {
37
+ id: 'e3',
38
+ status: 'Degraded',
39
+ tone: 'warning',
40
+ title: 'Error rate above 2%',
41
+ description: 'Alert fired on orders-api.',
42
+ at: FIXTURE_NOW - 5 * HOUR,
43
+ actor: 'Monitoring',
44
+ },
45
+ {
46
+ id: 'e2',
47
+ status: 'Failed',
48
+ tone: 'destructive',
49
+ title: 'Build failed',
50
+ description: 'Type check failed in 2 files.',
51
+ at: FIXTURE_NOW - 26 * HOUR,
52
+ actor: 'CI',
53
+ },
54
+ {
55
+ id: 'e1',
56
+ status: 'Created',
57
+ tone: 'neutral',
58
+ title: 'Deployment created',
59
+ at: FIXTURE_NOW - 50 * HOUR,
60
+ actor: 'Ana Lopez',
61
+ },
62
+ ];
@@ -0,0 +1,34 @@
1
+ // @gntik-ai/blocks — data-display family.
2
+ export * from './shared/TrendDelta';
3
+ export * from './KpiRow/KpiRow';
4
+ export { meta as kpiRowMeta } from './KpiRow/block.meta';
5
+ export { KPI_ITEMS } from './KpiRow/fixtures';
6
+ export * from './StatCard/StatCard';
7
+ export { meta as statCardMeta } from './StatCard/block.meta';
8
+ export { STAT_RANGES } from './StatCard/fixtures';
9
+ export * from './ChartCard/ChartCard';
10
+ export { meta as chartCardMeta } from './ChartCard/block.meta';
11
+ export {
12
+ COST_BY_RANGE,
13
+ COST_RANGES,
14
+ TRAFFIC_BUCKETS,
15
+ TRAFFIC_BY_RANGE,
16
+ TRAFFIC_RANGES,
17
+ type CostPoint,
18
+ type TrafficRow,
19
+ } from './ChartCard/fixtures';
20
+ export * from './DescriptionListCard/DescriptionListCard';
21
+ export { meta as descriptionListCardMeta } from './DescriptionListCard/block.meta';
22
+ export { DEPLOYMENT_DETAILS } from './DescriptionListCard/fixtures';
23
+ export * from './ResourceCardGrid/ResourceCardGrid';
24
+ export { meta as resourceCardGridMeta } from './ResourceCardGrid/block.meta';
25
+ export { RESOURCE_CARDS } from './ResourceCardGrid/fixtures';
26
+ export * from './StatusTimeline/StatusTimeline';
27
+ export { meta as statusTimelineMeta } from './StatusTimeline/block.meta';
28
+ export { DEPLOYMENT_EVENTS } from './StatusTimeline/fixtures';
29
+ export * from './ActivityFeed/ActivityFeed';
30
+ export { meta as activityFeedMeta } from './ActivityFeed/block.meta';
31
+ export { PROJECT_ACTIVITY } from './ActivityFeed/fixtures';
32
+ export * from './CommentThread/CommentThread';
33
+ export { meta as commentThreadMeta } from './CommentThread/block.meta';
34
+ export { COMMENT_AUTHOR, REVIEW_COMMENTS } from './CommentThread/fixtures';
@@ -0,0 +1,60 @@
1
+ import { cn } from '@gntik-ai/ui';
2
+
3
+ /** Arrow shape of a change. */
4
+ export type DeltaDirection = 'up' | 'down' | 'flat';
5
+ /** Whether the change is good, bad or neither — independent of its direction (lower latency is good). */
6
+ export type DeltaSentiment = 'positive' | 'negative' | 'neutral';
7
+
8
+ export interface KpiDelta {
9
+ /** Display text, e.g. "+12.4%". */
10
+ value: string;
11
+ direction: DeltaDirection;
12
+ sentiment: DeltaSentiment;
13
+ /** Comparison context, e.g. "vs. previous 24h". */
14
+ note?: string;
15
+ }
16
+
17
+ const SENTIMENT_CLASS: Record<DeltaSentiment, string> = {
18
+ positive: 'text-success-text',
19
+ negative: 'text-destructive-text',
20
+ neutral: 'text-muted-foreground',
21
+ };
22
+
23
+ const DIRECTION_TEXT: Record<DeltaDirection, string> = { up: 'Increased by', down: 'Decreased by', flat: 'Unchanged:' };
24
+ const SENTIMENT_TEXT: Record<DeltaSentiment, string> = {
25
+ positive: 'favourable',
26
+ negative: 'unfavourable',
27
+ neutral: '',
28
+ };
29
+
30
+ export interface TrendDeltaProps extends KpiDelta {
31
+ className?: string;
32
+ }
33
+
34
+ /**
35
+ * Mono delta with a triangle: the arrow follows `direction`, the colour follows `sentiment`.
36
+ * Screen readers get the direction and sentiment in words.
37
+ */
38
+ export function TrendDelta({ value, direction, sentiment, className }: TrendDeltaProps) {
39
+ const sr = [DIRECTION_TEXT[direction], value, SENTIMENT_TEXT[sentiment] && `(${SENTIMENT_TEXT[sentiment]})`]
40
+ .filter(Boolean)
41
+ .join(' ');
42
+ return (
43
+ <span
44
+ data-sentiment={sentiment}
45
+ className={cn(
46
+ 'inline-flex items-center gap-1 font-mono text-[12px] font-medium tabular-nums',
47
+ SENTIMENT_CLASS[sentiment],
48
+ className,
49
+ )}
50
+ >
51
+ <svg width="9" height="9" viewBox="0 0 10 10" aria-hidden className="shrink-0">
52
+ {direction === 'up' && <path d="M5 1.5 9 8.5H1z" fill="currentColor" />}
53
+ {direction === 'down' && <path d="M5 8.5 1 1.5h8z" fill="currentColor" />}
54
+ {direction === 'flat' && <path d="M1 4h8v2H1z" fill="currentColor" />}
55
+ </svg>
56
+ <span aria-hidden>{value}</span>
57
+ <span className="sr-only">{sr}</span>
58
+ </span>
59
+ );
60
+ }
@@ -0,0 +1,47 @@
1
+ import { render, screen, waitFor } from '@testing-library/react';
2
+ import userEvent from '@testing-library/user-event';
3
+ import { expectNoAxeViolations } from '../../test/a11y';
4
+ import { ConfirmDestructive } from './ConfirmDestructive';
5
+
6
+ describe('ConfirmDestructive', () => {
7
+ it('renders the trigger and an accessible dialog', async () => {
8
+ const { container } = render(<ConfirmDestructive />);
9
+ await expectNoAxeViolations(container);
10
+ await userEvent.setup().click(screen.getByRole('button', { name: 'Delete project' }));
11
+ const dialog = await screen.findByRole('alertdialog', { name: 'Delete project?' });
12
+ expect(dialog).toBeInTheDocument();
13
+ await expectNoAxeViolations(document.body);
14
+ });
15
+
16
+ it('unlocks only on an exact name match, then confirms and closes', async () => {
17
+ const user = userEvent.setup();
18
+ const onConfirm = vi.fn(() => Promise.resolve());
19
+ render(<ConfirmDestructive defaultOpen triggerLabel={null} onConfirm={onConfirm} />);
20
+ const dialog = await screen.findByRole('alertdialog');
21
+ const confirm = screen.getAllByRole('button', { name: 'Delete project' }).at(-1)!;
22
+ expect(confirm).toBeDisabled();
23
+ const input = screen.getByRole('textbox', { name: 'Type acme-web to confirm' });
24
+ await user.type(input, 'acme-we');
25
+ expect(confirm).toBeDisabled();
26
+ await user.type(input, 'b');
27
+ expect(confirm).toBeEnabled();
28
+ await user.click(confirm);
29
+ expect(onConfirm).toHaveBeenCalledTimes(1);
30
+ await waitFor(() => expect(dialog).not.toBeInTheDocument());
31
+ });
32
+
33
+ it('shows the error when confirm rejects', async () => {
34
+ const user = userEvent.setup();
35
+ render(
36
+ <ConfirmDestructive
37
+ defaultOpen
38
+ triggerLabel={null}
39
+ resourceName="db"
40
+ onConfirm={() => Promise.reject(new Error('Project is locked'))}
41
+ />,
42
+ );
43
+ await user.type(await screen.findByRole('textbox'), 'db{Enter}');
44
+ expect(await screen.findByRole('alert')).toHaveTextContent('Project is locked');
45
+ expect(screen.getByRole('alertdialog')).toBeInTheDocument();
46
+ });
47
+ });