@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,218 @@
1
+ import {
2
+ AreaChart,
3
+ BarChart,
4
+ Heatmap,
5
+ LineChart,
6
+ chartFmt,
7
+ type ChartColor,
8
+ type ChartStateProps,
9
+ type HeatmapProps,
10
+ type ValueFormatter,
11
+ } from '@gntik-ai/charts';
12
+ import { Card, CardAction, CardDescription, CardHeader, CardTitle, cn, Toggle, ToggleGroup } from '@gntik-ai/ui';
13
+ import { useEffect, useRef, useState, type ReactNode } from 'react';
14
+ import { TrendDelta, type KpiDelta } from '../shared/TrendDelta';
15
+ import { COST_BY_RANGE, COST_RANGES, type CostPoint } from './fixtures';
16
+
17
+ export interface ChartCardRange {
18
+ /** Range key ("7d"); also the key into `dataByRange`. */
19
+ value: string;
20
+ label: string;
21
+ /** Optional headline for this range. */
22
+ summary?: { value: string; delta?: KpiDelta };
23
+ }
24
+
25
+ /** Heatmap-only options of a ChartCard with `kind="heatmap"`. */
26
+ export type ChartCardHeatmapOptions = Pick<
27
+ HeatmapProps<object>,
28
+ 'scale' | 'domain' | 'steps' | 'showValues' | 'rowHeader'
29
+ >;
30
+
31
+ export interface ChartCardProps<T extends object> extends ChartStateProps {
32
+ title?: string;
33
+ description?: string;
34
+ /** Ordered options; defaults to 7d/30d/90d. Unknown range keys fall back to the first option. */
35
+ ranges?: readonly ChartCardRange[];
36
+ /** Chart rows per range key. */
37
+ dataByRange?: Readonly<Record<string, readonly T[]>>;
38
+ /** X-axis field (heatmap: the row label field). */
39
+ index?: Extract<keyof T, string>;
40
+ /** Series fields, one legend entry each (heatmap: the columns, in order). */
41
+ categories?: ReadonlyArray<Extract<keyof T, string>>;
42
+ /**
43
+ * Chart type. `heatmap` renders an @gntik-ai/charts Heatmap: rows from `index`, columns from
44
+ * `categories`, its hue from `colors[0]`, its colour scale as the legend (`showLegend`) and its
45
+ * cells sized to fill `height`.
46
+ */
47
+ kind?: 'area' | 'line' | 'bar' | 'heatmap';
48
+ /** Heatmap options (scale, domain, steps, showValues, rowHeader). */
49
+ heatmap?: ChartCardHeatmapOptions;
50
+ stacked?: boolean;
51
+ colors?: readonly ChartColor[];
52
+ valueFormatter?: ValueFormatter;
53
+ showLegend?: boolean;
54
+ /** Hide the range control when a page toolbar owns the controlled `range`. */
55
+ showRangeToggle?: boolean;
56
+ /** Plot height in px (heatmap: the grid, column labels included). */
57
+ height?: number;
58
+ /** Initial uncontrolled selection; defaults to the first option. */
59
+ defaultRange?: string;
60
+ /** Controlled selection. Changes are requested through `onRangeChange` until this prop updates. */
61
+ range?: string;
62
+ onRangeChange?: (range: string) => void;
63
+ /** Extra header control (e.g. a MoreMenu), after the range toggle. */
64
+ action?: ReactNode;
65
+ /** Footer content under the chart. */
66
+ footer?: ReactNode;
67
+ /** Heading level of the title, to fit the page outline (default h3). */
68
+ titleAs?: 'h2' | 'h3' | 'h4';
69
+ className?: string;
70
+ }
71
+
72
+ const DEFAULT_CATEGORIES = ['Compute', 'Storage', 'Network'] as const;
73
+ const DEFAULT_COLORS: readonly ChartColor[] = ['primary', 'violet', 'cyan'];
74
+
75
+ /**
76
+ * Chart in a Card: title, optional headline value + delta, a configurable range ToggleGroup, and an
77
+ * @gntik-ai/charts area, line, bar or heatmap chart with its legend (heatmap: its colour scale).
78
+ * Defaults to 7d/30d/90d; hide the toggle when a toolbar owns the controlled range.
79
+ * The chart states (`state`, `emptyMessage`, `errorMessage`, `onRetry`) and `dataTable` pass through.
80
+ */
81
+ export function ChartCard<T extends object = CostPoint>({
82
+ title = 'Infrastructure cost',
83
+ description = 'Spend by resource',
84
+ ranges = COST_RANGES,
85
+ dataByRange = COST_BY_RANGE as unknown as Readonly<Record<string, readonly T[]>>,
86
+ index = 'day' as Extract<keyof T, string>,
87
+ categories = DEFAULT_CATEGORIES as unknown as ReadonlyArray<Extract<keyof T, string>>,
88
+ kind = 'area',
89
+ heatmap,
90
+ stacked = true,
91
+ colors = DEFAULT_COLORS,
92
+ valueFormatter = chartFmt.usd,
93
+ showLegend = true,
94
+ showRangeToggle = true,
95
+ height = 240,
96
+ defaultRange,
97
+ range: rangeProp,
98
+ onRangeChange,
99
+ action,
100
+ footer,
101
+ titleAs = 'h3',
102
+ className,
103
+ state,
104
+ emptyMessage,
105
+ errorMessage,
106
+ onRetry,
107
+ dataTable,
108
+ }: ChartCardProps<T>) {
109
+ const tabEntry = useRef(false);
110
+ useEffect(() => {
111
+ const onKeyDown = (event: KeyboardEvent) => {
112
+ tabEntry.current = event.key === 'Tab';
113
+ };
114
+ const clearTabEntry = () => {
115
+ tabEntry.current = false;
116
+ };
117
+ document.addEventListener('keydown', onKeyDown, true);
118
+ document.addEventListener('keyup', clearTabEntry, true);
119
+ window.addEventListener('blur', clearTabEntry);
120
+ return () => {
121
+ document.removeEventListener('keydown', onKeyDown, true);
122
+ document.removeEventListener('keyup', clearTabEntry, true);
123
+ window.removeEventListener('blur', clearTabEntry);
124
+ };
125
+ }, []);
126
+ const [inner, setInner] = useState(defaultRange ?? ranges[0]?.value ?? '');
127
+ const current = rangeProp ?? inner;
128
+ const matched = ranges.find((r) => r.value === current);
129
+ const active = matched ?? ranges[0];
130
+ useEffect(() => {
131
+ if (!matched && ranges.length > 0 && process.env.NODE_ENV === 'development') {
132
+ console.warn('ChartCard: range/defaultRange is not in ranges; falling back to the first option.');
133
+ }
134
+ }, [current, matched, ranges.length]);
135
+ const data = (active && dataByRange[active.value]) ?? [];
136
+ const chartLabel = `${title}${active ? `, ${active.label}` : ''}`;
137
+ const states = { state, emptyMessage, errorMessage, onRetry, dataTable };
138
+ const common = {
139
+ data,
140
+ index,
141
+ categories,
142
+ colors,
143
+ valueFormatter,
144
+ showLegend,
145
+ height,
146
+ 'aria-label': chartLabel,
147
+ ...states,
148
+ };
149
+ return (
150
+ <Card className={className}>
151
+ <CardHeader className="pb-2">
152
+ <CardTitle as={titleAs}>{title}</CardTitle>
153
+ {description && <CardDescription>{description}</CardDescription>}
154
+ <CardAction className="flex items-center gap-2">
155
+ {showRangeToggle && ranges.length > 1 && (
156
+ <ToggleGroup
157
+ aria-label={`${title} range`}
158
+ size="sm"
159
+ value={active ? [active.value] : []}
160
+ onFocus={(event) => {
161
+ // Tab enters at the pressed option; pointer and restored focus stay on their original option.
162
+ if (!tabEntry.current) return;
163
+ if (event.relatedTarget instanceof Node && event.currentTarget.contains(event.relatedTarget)) return;
164
+ const selected = event.currentTarget.querySelector<HTMLButtonElement>('[aria-pressed="true"]');
165
+ selected?.focus();
166
+ }}
167
+ onValueChange={(v) => {
168
+ const next = v[0];
169
+ if (!next) return;
170
+ if (rangeProp === undefined) setInner(next);
171
+ onRangeChange?.(next);
172
+ }}
173
+ >
174
+ {ranges.map((r) => (
175
+ <Toggle key={r.value} value={r.value} className="font-mono">
176
+ {r.label}
177
+ </Toggle>
178
+ ))}
179
+ </ToggleGroup>
180
+ )}
181
+ {action}
182
+ </CardAction>
183
+ </CardHeader>
184
+ {active?.summary && (
185
+ <p className="flex flex-wrap items-baseline gap-x-2 px-5" aria-live="polite">
186
+ <span className="text-[24px] font-semibold tracking-tight text-foreground tabular-nums">
187
+ {active.summary.value}
188
+ </span>
189
+ {active.summary.delta && <TrendDelta {...active.summary.delta} />}
190
+ </p>
191
+ )}
192
+ <div className={cn('px-3 pb-4 pt-3 sm:px-4', !footer && 'pb-5')}>
193
+ {kind === 'heatmap' ? (
194
+ <Heatmap
195
+ data={data}
196
+ index={index}
197
+ categories={categories}
198
+ color={colors[0] ?? 'primary'}
199
+ valueFormatter={valueFormatter}
200
+ showScale={showLegend}
201
+ height={height}
202
+ aria-label={chartLabel}
203
+ {...heatmap}
204
+ {...states}
205
+ dataTable={dataTable ?? true}
206
+ />
207
+ ) : kind === 'bar' ? (
208
+ <BarChart {...common} stacked={stacked} />
209
+ ) : kind === 'line' ? (
210
+ <LineChart {...common} />
211
+ ) : (
212
+ <AreaChart {...common} stacked={stacked} />
213
+ )}
214
+ </div>
215
+ {footer && <div className="border-t border-border px-5 py-3 text-[12.5px] text-muted-foreground">{footer}</div>}
216
+ </Card>
217
+ );
218
+ }
@@ -0,0 +1,10 @@
1
+ import type { BlockMeta } from '../../meta';
2
+
3
+ export const meta: BlockMeta = {
4
+ name: 'Chart card',
5
+ family: 'data-display',
6
+ status: 'beta',
7
+ description:
8
+ 'A Card with chart and legend: default 7d/30d/90d or custom ranges via ranges, controlled range/onRangeChange, and showRangeToggle={false} when a toolbar owns the range (pass its window as range). Supports title, optional headline and delta, area/line/bar/heatmap charts, and loading, empty and error states. Supply matching dataByRange keys and use defaultRange for initial uncontrolled selection; the toolbar is the only range control when the toggle is hidden. Unknown range/defaultRange keys fall back to the first option for selection, data and label, with a development-only warning.',
9
+ uses: ['Card', 'ToggleGroup', 'AreaChart', 'LineChart', 'BarChart', 'Heatmap', 'ChartLegend'],
10
+ };
@@ -0,0 +1,37 @@
1
+ import { ChartCard, type ChartCardRange } from '../ChartCard';
2
+
3
+ export const REQUEST_RANGES: readonly ChartCardRange[] = [
4
+ { value: '24h', label: '24h', summary: { value: '1,240 requests' } },
5
+ { value: '7d', label: '7d', summary: { value: '8,680 requests' } },
6
+ { value: '30d', label: '30d', summary: { value: '37,200 requests' } },
7
+ ];
8
+
9
+ export const REQUEST_DATA = {
10
+ '24h': [
11
+ { time: '00:00', Requests: 580 },
12
+ { time: '12:00', Requests: 660 },
13
+ ],
14
+ '7d': [
15
+ { time: 'Mon', Requests: 4080 },
16
+ { time: 'Sun', Requests: 4600 },
17
+ ],
18
+ '30d': [
19
+ { time: 'Week 1', Requests: 18000 },
20
+ { time: 'Week 4', Requests: 19200 },
21
+ ],
22
+ };
23
+
24
+ /** Ordered consumer windows replace the default 7d/30d/90d options. */
25
+ export default function CustomRangeChartCard() {
26
+ return (
27
+ <ChartCard
28
+ title="Requests"
29
+ description="Request volume over the selected window"
30
+ ranges={REQUEST_RANGES}
31
+ dataByRange={REQUEST_DATA}
32
+ index="time"
33
+ categories={['Requests']}
34
+ valueFormatter={(value) => `${value.toLocaleString('en-US')} requests`}
35
+ />
36
+ );
37
+ }
@@ -0,0 +1,20 @@
1
+ import { ChartCard } from '../ChartCard';
2
+ import { TRAFFIC_BUCKETS, TRAFFIC_BY_RANGE, TRAFFIC_RANGES, type TrafficRow } from '../fixtures';
3
+
4
+ /** A ChartCard with a heatmap: weekday × time-of-day traffic, colour scale as the legend. */
5
+ export default function HeatmapChartCard() {
6
+ return (
7
+ <ChartCard<TrafficRow>
8
+ kind="heatmap"
9
+ title="Traffic by hour"
10
+ description="Requests per weekday, in thousands"
11
+ ranges={TRAFFIC_RANGES}
12
+ dataByRange={TRAFFIC_BY_RANGE}
13
+ index="day"
14
+ categories={TRAFFIC_BUCKETS}
15
+ valueFormatter={(v) => `${v}k`}
16
+ heatmap={{ rowHeader: 'Day', showValues: false }}
17
+ height={240}
18
+ />
19
+ );
20
+ }
@@ -0,0 +1,34 @@
1
+ import { SimpleSelect } from '@gntik-ai/ui';
2
+ import { useState } from 'react';
3
+ import { ChartCard } from '../ChartCard';
4
+ import { REQUEST_DATA, REQUEST_RANGES } from './custom-ranges';
5
+
6
+ /** The page toolbar owns the only range control; each card follows its controlled window. */
7
+ export default function ToolbarOwnedRangeChartCard() {
8
+ const [range, setRange] = useState('24h');
9
+ return (
10
+ <div className="grid gap-4">
11
+ <SimpleSelect
12
+ label="Page window"
13
+ items={REQUEST_RANGES}
14
+ value={range}
15
+ onValueChange={(value) => {
16
+ if (value !== null) setRange(value);
17
+ }}
18
+ />
19
+ <ChartCard
20
+ title="Requests"
21
+ description="Request volume over the page window"
22
+ ranges={REQUEST_RANGES}
23
+ range={range}
24
+ showRangeToggle={false}
25
+ dataByRange={REQUEST_DATA}
26
+ index="time"
27
+ categories={['Requests']}
28
+ valueFormatter={(value) => `${value.toLocaleString('en-US')} requests`}
29
+ emptyMessage="No requests in this window"
30
+ errorMessage="Request volume unavailable"
31
+ />
32
+ </div>
33
+ );
34
+ }
@@ -0,0 +1,82 @@
1
+ import type { ChartCardRange } from './ChartCard';
2
+
3
+ export interface CostPoint {
4
+ day: string;
5
+ Compute: number;
6
+ Storage: number;
7
+ Network: number;
8
+ }
9
+
10
+ const series = (labels: string[], base: [number, number, number], step: [number, number, number]): CostPoint[] =>
11
+ labels.map((day, i) => ({
12
+ day,
13
+ Compute: Math.round(base[0] + step[0] * i + ((i * 37) % 11) * 4),
14
+ Storage: Math.round(base[1] + step[1] * i + ((i * 13) % 7) * 2),
15
+ Network: Math.round(base[2] + step[2] * i + ((i * 29) % 5) * 3),
16
+ }));
17
+
18
+ const DAYS7 = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
19
+ const DAYS30 = Array.from({ length: 30 }, (_, i) => `Day ${i + 1}`);
20
+ const WEEKS13 = Array.from({ length: 13 }, (_, i) => `Week ${i + 1}`);
21
+
22
+ /** Infrastructure cost by resource, per range. */
23
+ export const COST_BY_RANGE: Record<string, CostPoint[]> = {
24
+ '7d': series(DAYS7, [180, 42, 120], [6, 1, 2]),
25
+ '30d': series(DAYS30, [160, 40, 110], [2, 0.4, 1]),
26
+ '90d': series(WEEKS13, [1100, 280, 760], [40, 6, 18]),
27
+ };
28
+
29
+ export const COST_RANGES: ChartCardRange[] = [
30
+ {
31
+ value: '7d',
32
+ label: '7d',
33
+ summary: { value: '$2,412', delta: { value: '+9.7%', direction: 'up', sentiment: 'negative' } },
34
+ },
35
+ {
36
+ value: '30d',
37
+ label: '30d',
38
+ summary: { value: '$9,880', delta: { value: '+4.1%', direction: 'up', sentiment: 'negative' } },
39
+ },
40
+ {
41
+ value: '90d',
42
+ label: '90d',
43
+ summary: { value: '$27,306', delta: { value: '−2.3%', direction: 'down', sentiment: 'positive' } },
44
+ },
45
+ ];
46
+
47
+ export interface TrafficRow {
48
+ day: string;
49
+ '00h': number;
50
+ '03h': number;
51
+ '06h': number;
52
+ '09h': number;
53
+ '12h': number;
54
+ '15h': number;
55
+ '18h': number;
56
+ '21h': number;
57
+ }
58
+
59
+ /** Three-hour buckets of TRAFFIC_BY_RANGE, in display order. */
60
+ export const TRAFFIC_BUCKETS = ['00h', '03h', '06h', '09h', '12h', '15h', '18h', '21h'] as const;
61
+
62
+ const SHAPE = [0.18, 0.12, 0.3, 0.82, 1, 0.94, 0.7, 0.4];
63
+
64
+ /** Requests (thousands) per weekday and three-hour bucket: a heatmap for a ChartCard. */
65
+ export const TRAFFIC_BY_RANGE: Record<string, TrafficRow[]> = {
66
+ '7d': DAYS7.map((day, d) => {
67
+ const weekend = d >= 5 ? 0.45 : 1;
68
+ const row = { day } as TrafficRow;
69
+ TRAFFIC_BUCKETS.forEach((bucket, b) => {
70
+ row[bucket] = Math.round((SHAPE[b] ?? 0) * weekend * (42 + ((d * 7 + b * 3) % 9)));
71
+ });
72
+ return row;
73
+ }),
74
+ };
75
+
76
+ export const TRAFFIC_RANGES: ChartCardRange[] = [
77
+ {
78
+ value: '7d',
79
+ label: 'Last 7 days',
80
+ summary: { value: '4.8M requests', delta: { value: '+6.2%', direction: 'up', sentiment: 'positive' } },
81
+ },
82
+ ];
@@ -0,0 +1,29 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import userEvent from '@testing-library/user-event';
3
+ import { expectNoAxeViolations } from '../../test/a11y';
4
+ import { CommentThread } from './CommentThread';
5
+
6
+ describe('CommentThread', () => {
7
+ it('renders the comments and an empty composer', async () => {
8
+ const { container } = render(<CommentThread />);
9
+ expect(screen.getByRole('list', { name: '3 comments' })).toBeInTheDocument();
10
+ expect(screen.getByRole('button', { name: 'Comment' })).toBeDisabled();
11
+ await expectNoAxeViolations(container);
12
+ });
13
+
14
+ it('posts a reply with the button and with Ctrl+Enter', async () => {
15
+ const user = userEvent.setup();
16
+ const onSubmit = vi.fn();
17
+ render(<CommentThread onSubmit={onSubmit} />);
18
+ const box = screen.getByRole('textbox', { name: 'Write a comment' });
19
+ await user.type(box, ' Merged, thanks! ');
20
+ await user.click(screen.getByRole('button', { name: 'Comment' }));
21
+ expect(onSubmit).toHaveBeenCalledWith('Merged, thanks!');
22
+ expect(screen.getByText('Merged, thanks!')).toBeInTheDocument();
23
+ expect(box).toHaveValue('');
24
+ await user.type(box, 'Follow-up');
25
+ await user.keyboard('{Control>}{Enter}{/Control}');
26
+ expect(onSubmit).toHaveBeenLastCalledWith('Follow-up');
27
+ expect(screen.getByRole('list', { name: '5 comments' })).toBeInTheDocument();
28
+ });
29
+ });
@@ -0,0 +1,120 @@
1
+ import { Avatar, Button, cn, Textarea, Timestamp, type DateInput, useI18n } from '@gntik-ai/ui';
2
+ import { useId, useState, type FormEvent, type KeyboardEvent } from 'react';
3
+ import { COMMENT_AUTHOR, REVIEW_COMMENTS } from './fixtures';
4
+
5
+ export interface CommentAuthor {
6
+ name: string;
7
+ src?: string;
8
+ }
9
+
10
+ export interface ThreadComment {
11
+ id: string;
12
+ author: CommentAuthor;
13
+ /** Plain text (no markdown); line breaks are kept. */
14
+ body: string;
15
+ at: DateInput;
16
+ }
17
+
18
+ export interface CommentThreadProps {
19
+ comments?: readonly ThreadComment[];
20
+ /** Author of new comments (the signed-in user). */
21
+ currentUser?: CommentAuthor;
22
+ /** Called with the trimmed text when a comment is posted. */
23
+ onSubmit?: (body: string) => void;
24
+ /** Append posted comments locally (default). Turn off when the parent updates `comments`. */
25
+ appendOnSubmit?: boolean;
26
+ /** Accessible name of the thread. */
27
+ label?: string;
28
+ placeholder?: string;
29
+ submitLabel?: string;
30
+ className?: string;
31
+ }
32
+
33
+ /** Comment thread: avatar, author, plain-text body and Timestamp, with a reply composer (⌘/Ctrl+Enter posts). */
34
+ export function CommentThread({
35
+ comments = REVIEW_COMMENTS,
36
+ currentUser = COMMENT_AUTHOR,
37
+ onSubmit,
38
+ appendOnSubmit = true,
39
+ label: labelProp,
40
+ placeholder: placeholderProp,
41
+ submitLabel: submitLabelProp,
42
+ className,
43
+ }: CommentThreadProps) {
44
+ const { t } = useI18n();
45
+ const label = labelProp ?? t('comments.label');
46
+ const placeholder = placeholderProp ?? t('comments.placeholder');
47
+ const submitLabel = submitLabelProp ?? t('comments.submit');
48
+ const [draft, setDraft] = useState('');
49
+ const [posted, setPosted] = useState<ThreadComment[]>([]);
50
+ const hintId = useId();
51
+ const all = appendOnSubmit ? [...comments, ...posted] : comments;
52
+ const text = draft.trim();
53
+
54
+ const post = () => {
55
+ if (!text) return;
56
+ onSubmit?.(text);
57
+ if (appendOnSubmit)
58
+ setPosted((prev) => [
59
+ ...prev,
60
+ { id: `local-${prev.length + 1}`, author: currentUser, body: text, at: Date.now() },
61
+ ]);
62
+ setDraft('');
63
+ };
64
+ const submit = (event: FormEvent) => {
65
+ event.preventDefault();
66
+ post();
67
+ };
68
+ const onKeyDown = (event: KeyboardEvent<HTMLTextAreaElement>) => {
69
+ if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) {
70
+ event.preventDefault();
71
+ post();
72
+ }
73
+ };
74
+
75
+ return (
76
+ <section aria-label={label} className={cn('flex flex-col gap-6', className)}>
77
+ <ul aria-label={`${all.length} ${all.length === 1 ? 'comment' : 'comments'}`} className="flex flex-col gap-5">
78
+ {all.map((comment, i) => (
79
+ <li key={comment.id} className="relative flex gap-3">
80
+ {i < all.length - 1 && (
81
+ <span aria-hidden className="absolute top-9 -bottom-5 start-[15.5px] w-px bg-border" />
82
+ )}
83
+ <Avatar name={comment.author.name} src={comment.author.src} size="sm" className="relative z-10" />
84
+ <article className="min-w-0 flex-1 rounded-lg border border-border bg-background/50 p-3">
85
+ <header className="flex flex-wrap items-baseline justify-between gap-x-3 gap-y-0.5">
86
+ <span className="text-[12.5px] font-semibold text-foreground">{comment.author.name}</span>
87
+ <Timestamp value={comment.at} className="font-mono text-[11px]" />
88
+ </header>
89
+ <p className="mt-1 text-[13px] leading-relaxed whitespace-pre-line text-foreground/85">{comment.body}</p>
90
+ </article>
91
+ </li>
92
+ ))}
93
+ </ul>
94
+ <form onSubmit={submit} className="flex gap-3">
95
+ <Avatar name={currentUser.name} src={currentUser.src} size="sm" />
96
+ <div className="min-w-0 flex-1 rounded-lg border border-border bg-background/50 transition-colors focus-within:border-primary/50">
97
+ <Textarea
98
+ aria-label={t('comments.write')}
99
+ aria-describedby={hintId}
100
+ value={draft}
101
+ onValueChange={setDraft}
102
+ onKeyDown={onKeyDown}
103
+ placeholder={placeholder}
104
+ rows={3}
105
+ resize="none"
106
+ className="border-0 bg-transparent shadow-none"
107
+ />
108
+ <div className="flex items-center justify-between gap-2 border-t border-border/60 px-3 py-2">
109
+ <span id={hintId} className="text-[11.5px] text-muted-foreground">
110
+ Plain text · ⌘/Ctrl + Enter to post
111
+ </span>
112
+ <Button type="submit" size="sm" disabled={!text}>
113
+ {submitLabel}
114
+ </Button>
115
+ </div>
116
+ </div>
117
+ </form>
118
+ </section>
119
+ );
120
+ }
@@ -0,0 +1,10 @@
1
+ import type { BlockMeta } from '../../meta';
2
+
3
+ export const meta: BlockMeta = {
4
+ name: 'Comment thread',
5
+ family: 'data-display',
6
+ status: 'beta',
7
+ description:
8
+ 'Plain-text comments with avatar, author and Timestamp, plus a reply composer (Textarea + Button, ⌘/Ctrl+Enter posts).',
9
+ uses: ['Avatar', 'Timestamp', 'Textarea', 'Button'],
10
+ };
@@ -0,0 +1,28 @@
1
+ import type { ThreadComment, CommentAuthor } from './CommentThread';
2
+
3
+ const MIN = 60_000;
4
+ const NOW = Date.now();
5
+
6
+ export const COMMENT_AUTHOR: CommentAuthor = { name: 'Jordan Blake' };
7
+
8
+ /** Review discussion on a pull request, oldest first. */
9
+ export const REVIEW_COMMENTS: ThreadComment[] = [
10
+ {
11
+ id: 'c1',
12
+ author: { name: 'Ana Lopez' },
13
+ body: 'The retry policy now backs off exponentially. Can someone check the timeout on the invoice worker?',
14
+ at: NOW - 95 * MIN,
15
+ },
16
+ {
17
+ id: 'c2',
18
+ author: { name: 'Sam Carter' },
19
+ body: 'Looks good. 30 seconds is fine for the worker, the queue redelivers after 60.',
20
+ at: NOW - 70 * MIN,
21
+ },
22
+ {
23
+ id: 'c3',
24
+ author: { name: 'Priya Nair' },
25
+ body: 'Approved. Please add a changelog entry before merging.',
26
+ at: NOW - 12 * MIN,
27
+ },
28
+ ];
@@ -0,0 +1,28 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import userEvent from '@testing-library/user-event';
3
+ import { expectNoAxeViolations } from '../../test/a11y';
4
+ import { DescriptionListCard } from './DescriptionListCard';
5
+
6
+ describe('DescriptionListCard', () => {
7
+ it('renders labels and values', async () => {
8
+ const { container } = render(<DescriptionListCard onEdit={() => {}} />);
9
+ expect(screen.getByText('Deployment ID')).toBeInTheDocument();
10
+ expect(screen.getByText('dep_7f3c9a21')).toBeInTheDocument();
11
+ expect(screen.getByText('Running')).toBeInTheDocument();
12
+ await expectNoAxeViolations(container);
13
+ });
14
+
15
+ it('copies a value and calls onEdit', async () => {
16
+ const user = userEvent.setup();
17
+ const writeText = vi.spyOn(navigator.clipboard, 'writeText');
18
+ const onCopy = vi.fn();
19
+ const onEdit = vi.fn();
20
+ render(<DescriptionListCard onCopy={onCopy} onEdit={onEdit} />);
21
+ await user.click(screen.getByRole('button', { name: 'Copy Deployment ID' }));
22
+ expect(writeText).toHaveBeenCalledWith('dep_7f3c9a21');
23
+ expect(onCopy).toHaveBeenCalledWith(expect.objectContaining({ id: 'id' }));
24
+ expect(screen.getByRole('button', { name: 'Deployment ID copied' })).toBeInTheDocument();
25
+ await user.click(screen.getByRole('button', { name: 'Edit' }));
26
+ expect(onEdit).toHaveBeenCalled();
27
+ });
28
+ });