@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,343 @@
1
+ import { useEffect, useRef, useState, useSyncExternalStore, type ReactNode } from 'react';
2
+ import { Play } from '@gntik-ai/icons';
3
+ import { FlowCanvas, useFlowState, type FlowNode, type FlowNodeData, type FlowNodeKind } from '@gntik-ai/flow';
4
+ import { Button, ResizablePanel, ResizablePanelGroup, ResizeHandle, cn } from '@gntik-ai/ui';
5
+ import {
6
+ builderConsole,
7
+ builderEdges,
8
+ builderNodes,
9
+ builderPalette,
10
+ type ConsoleEntry,
11
+ type FlowEdge,
12
+ type PaletteItem,
13
+ } from './fixtures';
14
+ import type { FlowBuilderExtensions } from './extensions';
15
+ import { useDecoratedNodes } from './decorated-nodes';
16
+ import { useConnections } from './connections';
17
+ import { FlowProblems } from './problems';
18
+ import { NodeInspector, NodePalette, RunConsole } from './parts';
19
+
20
+ export type { ConsoleEntry, FlowEdge, PaletteItem } from './fixtures';
21
+ /** Graph types re-exported so apps can type nodes without depending on @gntik-ai/flow. */
22
+ export type { FlowNode, FlowNodeData, FlowNodeKind, FlowTone } from '@gntik-ai/flow';
23
+
24
+ export type { FlowConnection, FlowConnectionRejection, FlowValidationIssue } from './extensions';
25
+ export type { FlowNodeRenderer, FlowHandle, FlowNodeRenderProps } from '@gntik-ai/flow';
26
+
27
+ export interface FlowBuilderProps<Kind extends string = FlowNodeKind> extends FlowBuilderExtensions {
28
+ title?: string;
29
+ nodes?: FlowNode<Kind | FlowNodeKind>[];
30
+ edges?: FlowEdge[];
31
+ palette?: PaletteItem[];
32
+ /**
33
+ * Console output (controlled). Run and Clear then report the next list through
34
+ * `onConsoleChange` instead of updating it internally.
35
+ */
36
+ consoleEntries?: ConsoleEntry[];
37
+ /** Initial console output (uncontrolled). Defaults to the sample entries. */
38
+ defaultConsoleEntries?: ConsoleEntry[];
39
+ /** Called with the next console list after a run appends to it or it is cleared. */
40
+ onConsoleChange?: (entries: ConsoleEntry[]) => void;
41
+ /** Called when the console's Clear button is pressed. */
42
+ onConsoleClear?: () => void;
43
+ /** Heading level of the builder title (default h2); panel titles use the next level. */
44
+ titleAs?: 'h2' | 'h3' | 'h4';
45
+ /** Total height of the builder on wide screens (px). */
46
+ height?: number;
47
+ /**
48
+ * Called with the current graph when Run is pressed. Return console entries (or a promise
49
+ * of them) to log the real run; return nothing to log a simulated run. While a returned
50
+ * promise is pending the Run button shows a loading state; a rejection is logged as an error.
51
+ */
52
+ onRun?: (graph: {
53
+ nodes: FlowNode<Kind | FlowNodeKind>[];
54
+ edges: FlowEdge[];
55
+ }) => FlowRunResult | Promise<FlowRunResult>;
56
+ className?: string;
57
+ }
58
+
59
+ /** What `onRun` may return: entries to append, or nothing for the simulated run. */
60
+ export type FlowRunResult = ConsoleEntry[] | void | undefined;
61
+
62
+ const NO_ISSUES: NonNullable<FlowBuilderProps['validationIssues']> = [];
63
+ const WIDE_QUERY = '(min-width: 768px)';
64
+
65
+ function subscribeWide(onChange: () => void) {
66
+ if (typeof window === 'undefined' || !window.matchMedia) return () => {};
67
+ const mql = window.matchMedia(WIDE_QUERY);
68
+ mql.addEventListener?.('change', onChange);
69
+ return () => mql.removeEventListener?.('change', onChange);
70
+ }
71
+
72
+ function useIsWide() {
73
+ return useSyncExternalStore(
74
+ subscribeWide,
75
+ () => (typeof window !== 'undefined' && window.matchMedia ? window.matchMedia(WIDE_QUERY).matches : true),
76
+ () => true,
77
+ );
78
+ }
79
+
80
+ function clock() {
81
+ return new Date().toLocaleTimeString('en-GB', { hour12: false });
82
+ }
83
+
84
+ /**
85
+ * Visual flow builder: node palette, brand React Flow canvas (drag, connect, zoom/pan),
86
+ * inspector for the selected node and a run console, in resizable panels. On small screens
87
+ * the panels stack.
88
+ */
89
+ export function FlowBuilder<Kind extends string = FlowNodeKind>({
90
+ title = 'Untitled flow',
91
+ nodes: initialNodes = builderNodes,
92
+ edges: initialEdges = builderEdges,
93
+ palette = builderPalette,
94
+ consoleEntries,
95
+ defaultConsoleEntries = builderConsole,
96
+ onConsoleChange,
97
+ onConsoleClear,
98
+ titleAs: TitleTag = 'h2',
99
+ height = 600,
100
+ onRun,
101
+ nodeTypes,
102
+ onConnectEdge,
103
+ onConnect: onConnected,
104
+ isValidConnection,
105
+ onConnectionRejected,
106
+ validationIssues,
107
+ mode = 'edit',
108
+ nodeStatus,
109
+ onNodeSelect,
110
+ className,
111
+ }: FlowBuilderProps<Kind>) {
112
+ const runMode = mode === 'run';
113
+ const root = useRef<HTMLElement>(null);
114
+ const wide = useIsWide();
115
+ const panelAs = TitleTag === 'h2' ? 'h3' : TitleTag === 'h3' ? 'h4' : 'h5';
116
+ const {
117
+ nodes,
118
+ edges,
119
+ setNodes,
120
+ setEdges,
121
+ onNodesChange,
122
+ onEdgesChange,
123
+ onConnect: defaultConnect,
124
+ } = useFlowState<FlowNode<Kind | FlowNodeKind>, FlowEdge>(initialNodes, initialEdges);
125
+ const [innerLog, setInnerLog] = useState<ConsoleEntry[]>(defaultConsoleEntries);
126
+ const log = consoleEntries ?? innerLog;
127
+ const latestLog = useRef(log);
128
+ useEffect(() => {
129
+ latestLog.current = log;
130
+ });
131
+ const [running, setRunning] = useState(false);
132
+ const seq = useRef(0);
133
+ const runs = useRef(0);
134
+ const selected = nodes.find((n) => n.selected);
135
+
136
+ const addNode = (item: PaletteItem) => {
137
+ seq.current += 1;
138
+ const id = `${item.kind}-${seq.current}`;
139
+ const right = nodes.reduce((max, n) => Math.max(max, n.position.x), -256);
140
+ const slim = item.kind === 'trigger' || item.kind === 'router';
141
+ const node: FlowNode = {
142
+ id,
143
+ type: item.kind,
144
+ position: { x: right + 256, y: 112 + (seq.current % 3) * 32 },
145
+ width: 208,
146
+ height: slim ? 56 : 90,
147
+ selected: true,
148
+ data: { title: `New ${item.title.toLowerCase()}`, subtitle: item.description },
149
+ };
150
+ setNodes((ns) => [...ns.map((n) => (n.selected ? { ...n, selected: false } : n)), node]);
151
+ };
152
+
153
+ const updateNode = (id: string, patch: Partial<FlowNodeData>) =>
154
+ setNodes((ns) => ns.map((n) => (n.id === id ? { ...n, data: { ...n.data, ...patch } } : n)));
155
+
156
+ const deleteNode = (id: string) => {
157
+ setNodes((ns) => ns.filter((n) => n.id !== id));
158
+ setEdges((es) => es.filter((e) => e.source !== id && e.target !== id));
159
+ };
160
+
161
+ const commitLog = (next: ConsoleEntry[]) => {
162
+ latestLog.current = next;
163
+ if (consoleEntries === undefined) setInnerLog(next);
164
+ onConsoleChange?.(next);
165
+ };
166
+
167
+ const simulate = (n: number): ConsoleEntry[] => {
168
+ const ordered = [...nodes].sort((a, b) => a.position.x - b.position.x || a.position.y - b.position.y);
169
+ const t = clock();
170
+ return [
171
+ { id: `run-${n}-start`, time: t, level: 'info', message: `Run #${n} started · ${nodes.length} nodes` },
172
+ ...ordered.map<ConsoleEntry>((node, i) => ({
173
+ id: `run-${n}-${node.id}`,
174
+ time: t,
175
+ level: node.data.tone === 'failed' ? 'error' : node.data.tone === 'degraded' ? 'warn' : 'success',
176
+ message: `${node.data.title} ${node.data.tone === 'failed' ? 'failed' : 'completed'} in ${40 + ((i * 37) % 160)}ms`,
177
+ })),
178
+ { id: `run-${n}-end`, time: t, level: 'info', message: `Run #${n} finished` },
179
+ ];
180
+ };
181
+
182
+ const run = async () => {
183
+ runs.current += 1;
184
+ const n = runs.current;
185
+ const result = onRun?.({ nodes, edges });
186
+ let added: ConsoleEntry[];
187
+ if (result instanceof Promise) {
188
+ setRunning(true);
189
+ try {
190
+ added = (await result) ?? simulate(n);
191
+ } catch (error) {
192
+ const message = error instanceof Error ? error.message : String(error);
193
+ added = [{ id: `run-${n}-error`, time: clock(), level: 'error', message: `Run #${n} failed: ${message}` }];
194
+ } finally {
195
+ setRunning(false);
196
+ }
197
+ } else {
198
+ added = result ?? simulate(n);
199
+ }
200
+ commitLog([...latestLog.current, ...added]);
201
+ };
202
+
203
+ const clearLog = () => {
204
+ onConsoleClear?.();
205
+ commitLog([]);
206
+ };
207
+
208
+ const connections = useConnections(nodes, setEdges, defaultConnect, {
209
+ onConnect: onConnected,
210
+ onConnectEdge,
211
+ isValidConnection,
212
+ onConnectionRejected,
213
+ });
214
+
215
+ const issues = validationIssues ?? NO_ISSUES;
216
+ const displayedNodes = useDecoratedNodes(nodes, issues, nodeStatus, runMode);
217
+ const selectNode = (id: string) => {
218
+ setNodes((ns) => ns.map((n) => ({ ...n, selected: n.id === id })));
219
+ // Focus only within this builder; graph ids may repeat across independent canvases.
220
+ const element = root.current?.querySelector<HTMLElement>(`.react-flow__node[data-id="${CSS.escape(id)}"]`);
221
+ element?.focus();
222
+ onNodeSelect?.(id);
223
+ };
224
+
225
+ const canvas = (h: number | string) => (
226
+ <FlowCanvas<FlowNode<Kind | FlowNodeKind>, FlowEdge>
227
+ aria-label={`${title} canvas`}
228
+ height={h}
229
+ nodes={displayedNodes}
230
+ edges={edges}
231
+ onNodesChange={(changes) =>
232
+ onNodesChange(
233
+ runMode ? changes.filter((change) => change.type === 'select' || change.type === 'dimensions') : changes,
234
+ )
235
+ }
236
+ onEdgesChange={(changes) =>
237
+ onEdgesChange(runMode ? changes.filter((change) => change.type === 'select') : changes)
238
+ }
239
+ nodeRenderers={nodeTypes}
240
+ showInteractiveControls={!runMode}
241
+ elementsSelectable
242
+ nodesFocusable
243
+ nodesDraggable={!runMode}
244
+ nodesConnectable={!runMode}
245
+ deleteKeyCode={runMode ? null : 'Backspace'}
246
+ onNodeClick={(_, node) => onNodeSelect?.(node.id)}
247
+ onKeyDown={(event) => {
248
+ if (event.key !== 'Enter' && event.key !== ' ') return;
249
+ const element = (event.target as HTMLElement).closest('.react-flow__node');
250
+ const id = element?.getAttribute('data-id');
251
+ if (id) onNodeSelect?.(id);
252
+ }}
253
+ ariaLabelConfig={
254
+ runMode
255
+ ? {
256
+ 'node.a11yDescription.default': 'Press enter or space to select a node.',
257
+ }
258
+ : undefined
259
+ }
260
+ onConnect={runMode ? undefined : connections.connect}
261
+ isValidConnection={runMode ? () => false : connections.validate}
262
+ onConnectStart={connections.reset}
263
+ onClickConnectStart={connections.reset}
264
+ onConnectEnd={(_, state) => (state.toNode ? connections.finish() : connections.reset())}
265
+ onClickConnectEnd={connections.finish}
266
+ showMiniMap={wide}
267
+ />
268
+ );
269
+ const palettePanel = <NodePalette items={palette} onAdd={addNode} titleAs={panelAs} />;
270
+ const inspector = <NodeInspector node={selected} onChange={updateNode} onDelete={deleteNode} titleAs={panelAs} />;
271
+ const consolePanel = (
272
+ <div className="flex h-full min-h-0 flex-col">
273
+ <FlowProblems issues={issues} onSelect={selectNode} titleAs={panelAs} />
274
+ <RunConsole entries={log} onClear={clearLog} titleAs={panelAs} />
275
+ </div>
276
+ );
277
+
278
+ let body: ReactNode;
279
+ if (runMode) {
280
+ body = (
281
+ <div className="flex flex-col divide-y divide-border">
282
+ {canvas(wide ? height - 180 : 360)}
283
+ <div className="h-40">{consolePanel}</div>
284
+ </div>
285
+ );
286
+ } else if (wide) {
287
+ body = (
288
+ <ResizablePanelGroup direction="vertical" style={{ height }}>
289
+ <ResizablePanel defaultSize={72} minSize={40}>
290
+ <ResizablePanelGroup className="h-full">
291
+ <ResizablePanel defaultSize={18} minSize={12} maxSize={32} collapsible>
292
+ {palettePanel}
293
+ </ResizablePanel>
294
+ <ResizeHandle aria-label="Resize node palette" />
295
+ <ResizablePanel defaultSize={58} minSize={30}>
296
+ {canvas('100%')}
297
+ </ResizablePanel>
298
+ <ResizeHandle aria-label="Resize inspector" />
299
+ <ResizablePanel defaultSize={24} minSize={16} maxSize={40} collapsible>
300
+ {inspector}
301
+ </ResizablePanel>
302
+ </ResizablePanelGroup>
303
+ </ResizablePanel>
304
+ <ResizeHandle aria-label="Resize run console" />
305
+ <ResizablePanel defaultSize={28} minSize={12} collapsible>
306
+ {consolePanel}
307
+ </ResizablePanel>
308
+ </ResizablePanelGroup>
309
+ );
310
+ } else {
311
+ body = (
312
+ <div className="flex flex-col divide-y divide-border">
313
+ <div className="max-h-56">{palettePanel}</div>
314
+ {canvas(360)}
315
+ <div>{inspector}</div>
316
+ <div className="h-48">{consolePanel}</div>
317
+ </div>
318
+ );
319
+ }
320
+
321
+ return (
322
+ <section
323
+ ref={root}
324
+ aria-label={title}
325
+ className={cn('overflow-hidden rounded-lg border border-border bg-card shadow-sm', className)}
326
+ >
327
+ <header className="flex items-center justify-between gap-3 border-b border-border bg-secondary/35 px-3 py-2">
328
+ <div className="min-w-0">
329
+ <TitleTag className="truncate text-[13.5px] font-semibold text-foreground">{title}</TitleTag>
330
+ <p className="font-mono text-[11px] text-muted-foreground">
331
+ {nodes.length} nodes · {edges.length} edges
332
+ </p>
333
+ </div>
334
+ {!runMode && (
335
+ <Button size="sm" icon={Play} loading={running} onClick={() => void run()}>
336
+ Run
337
+ </Button>
338
+ )}
339
+ </header>
340
+ {body}
341
+ </section>
342
+ );
343
+ }
@@ -0,0 +1,20 @@
1
+ import type { BlockMeta } from '../../meta';
2
+
3
+ export const meta: BlockMeta = {
4
+ name: 'Flow builder',
5
+ family: 'builders',
6
+ status: 'beta',
7
+ description:
8
+ 'Visual flow editor with optional nodeTypes for named handles (branch-arm, parallel and sequence examples), onConnectEdge for edge type/data mapping, isValidConnection and onConnectionRejected for rules, onConnect for accepted connections, validationIssues for node/flow problems, mode="run" for a read-only canvas, nodeStatus for live badges, and onNodeSelect for pointer/keyboard selection. Optional nodeTypes registers kit-owned {type, handles:[{id,kind,position?,label?}], render, decorations?} definitions (no React Flow NodeProps). Keep nodeTypes and its renderer definitions stable (module scope or useMemo) across nodeStatus updates: recreating the array rebuilds custom node components and remounts them, defeating per-node memoization. onConnectEdge({source,target,sourceType,targetType,sourceHandle,targetHandle}) returns {type,data}; examples/extensions.tsx maps branch arm-0 to {kind: "branch-arm", armIndex: 0}, parallel branch to {kind: "parallel"}, and sequence next to {kind: "sequence"}. isValidConnection returns true, false, or {code,nodeId?,message}; onConnectionRejected reports {code,nodeId,message} once (nodeId defaults to the source) so consumers can forward it into validationIssues and control its lifetime, while onConnect reports accepted connections only. validationIssues decorates nodes with an icon and accessible text; node issues select and focus their node, and flow issues have no node link. mode="run" removes the Run action, palette, inspector and graph mutation while keeping labelled keyboard-selectable nodes, pan/zoom, fit controls and the wide-screen minimap. nodeStatus maps node IDs to FlowTone badges (running, degraded, failed, paused, done, handoff, review); onNodeSelect(id) handles pointer and keyboard selection. RunFlowExample shows live updates with per-node memoization. FlowNode defaults to the built-in FlowNodeKind union; use FlowNode<string> (or a custom kind union) for custom graphs, inferred by FlowBuilder and WorkflowBuilderPage. Custom renderers use kit status overlays and issue decorations by default; set decorations:"consumer" when render owns them, using data.decoration with accessible text and icons (for example, render:({data}) => <NodeCard title={data.title} tone={data.decoration?.status} />). FlowCanvas showInteractiveControls=false retains zoom/fit without the editing toggle. Named handles are keyboard controls: Enter or Space starts or completes a connection in edit mode; handles are disabled in run mode.',
9
+ uses: [
10
+ 'FlowCanvas',
11
+ 'useFlowState',
12
+ 'ResizablePanelGroup',
13
+ 'ResizablePanel',
14
+ 'ResizeHandle',
15
+ 'Button',
16
+ 'Field',
17
+ 'Input',
18
+ 'SimpleSelect',
19
+ ],
20
+ };
@@ -0,0 +1,67 @@
1
+ import { useRef, type Dispatch, type SetStateAction } from 'react';
2
+ import type { FlowCanvasProps, FlowNode } from '@gntik-ai/flow';
3
+ import type { FlowBuilderExtensions, FlowConnection, FlowConnectionRejection } from './extensions';
4
+ import type { FlowEdge } from './fixtures';
5
+
6
+ type Connect = NonNullable<FlowCanvasProps<FlowNode<string>, FlowEdge>['onConnect']>;
7
+ type Candidate = Parameters<NonNullable<FlowCanvasProps<FlowNode<string>, FlowEdge>['isValidConnection']>>[0];
8
+
9
+ export function useConnections(
10
+ nodes: FlowNode<string>[],
11
+ setEdges: Dispatch<SetStateAction<FlowEdge[]>>,
12
+ defaultConnect: Connect,
13
+ props: FlowBuilderExtensions,
14
+ ) {
15
+ const pending = useRef<FlowConnectionRejection | null>(null);
16
+ const seq = useRef(0);
17
+ const describe = (c: Candidate): FlowConnection => ({
18
+ source: c.source,
19
+ target: c.target,
20
+ sourceHandle: c.sourceHandle ?? null,
21
+ targetHandle: c.targetHandle ?? null,
22
+ sourceType: nodes.find((n) => n.id === c.source)?.type ?? 'step',
23
+ targetType: nodes.find((n) => n.id === c.target)?.type ?? 'step',
24
+ });
25
+ const reset = () => {
26
+ pending.current = null;
27
+ };
28
+ const validate = (c: Candidate) => {
29
+ const result = props.isValidConnection?.(describe(c)) ?? true;
30
+ pending.current =
31
+ result === true
32
+ ? null
33
+ : result === false
34
+ ? {
35
+ code: 'invalid-connection',
36
+ nodeId: c.source,
37
+ message: 'This connection is not allowed.',
38
+ }
39
+ : { ...result, nodeId: result.nodeId ?? c.source };
40
+ return result === true;
41
+ };
42
+ const finish = () => {
43
+ const issue = pending.current;
44
+ reset();
45
+ if (!issue) return;
46
+ props.onConnectionRejected?.(issue);
47
+ };
48
+ const connect: Connect = (c) => {
49
+ if (!validate(c)) {
50
+ finish();
51
+ return;
52
+ }
53
+ if (props.onConnectEdge) {
54
+ const mapped = props.onConnectEdge(describe(c));
55
+ setEdges((es) => [
56
+ ...es,
57
+ {
58
+ ...c,
59
+ ...mapped,
60
+ id: `edge-${++seq.current}-${c.source}-${c.target}`,
61
+ },
62
+ ]);
63
+ } else defaultConnect(c);
64
+ props.onConnect?.(describe(c));
65
+ };
66
+ return { connect, validate, finish, reset };
67
+ }
@@ -0,0 +1,80 @@
1
+ import { useState } from 'react';
2
+ import type { FlowNode, FlowTone } from '@gntik-ai/flow';
3
+ import type { FlowValidationIssue } from './extensions';
4
+
5
+ type Statuses = Readonly<Record<string, FlowTone | undefined>> | undefined;
6
+ interface CachedNode<Kind extends string> {
7
+ original: FlowNode<Kind>;
8
+ status: FlowTone | undefined;
9
+ issuesKey: string;
10
+ runMode: boolean;
11
+ node: FlowNode<Kind>;
12
+ }
13
+
14
+ function project<Kind extends string>(
15
+ nodes: FlowNode<Kind>[],
16
+ issues: readonly FlowValidationIssue[],
17
+ statuses: Statuses,
18
+ runMode: boolean,
19
+ previous: ReadonlyMap<string, CachedNode<Kind>>,
20
+ ) {
21
+ const cache = new Map<string, CachedNode<Kind>>();
22
+ const displayed = nodes.map((original) => {
23
+ const nodeIssues = issues.filter((issue) => issue.nodeId === original.id);
24
+ const issuesKey = JSON.stringify(nodeIssues);
25
+ const status = statuses?.[original.id];
26
+ const old = previous.get(original.id);
27
+ if (old?.original === original && old.status === status && old.issuesKey === issuesKey && old.runMode === runMode) {
28
+ cache.set(original.id, old);
29
+ return old.node;
30
+ }
31
+ const node: FlowNode<Kind> = {
32
+ ...original,
33
+ ...(runMode
34
+ ? {
35
+ draggable: false,
36
+ connectable: false,
37
+ deletable: false,
38
+ focusable: true,
39
+ selectable: true,
40
+ ariaLabel: original.data.title,
41
+ }
42
+ : {}),
43
+ data:
44
+ nodeIssues.length || status
45
+ ? {
46
+ ...original.data,
47
+ decoration: {
48
+ ...original.data.decoration,
49
+ status,
50
+ issues: nodeIssues,
51
+ },
52
+ }
53
+ : original.data,
54
+ };
55
+ cache.set(original.id, { original, status, issuesKey, runMode, node });
56
+ return node;
57
+ });
58
+ return { nodes, issues, statuses, runMode, cache, displayed };
59
+ }
60
+
61
+ /** Derive a snapshot when inputs change; preserve other nodes' identities without mutating a render cache. */
62
+ export function useDecoratedNodes<Kind extends string>(
63
+ nodes: FlowNode<Kind>[],
64
+ issues: readonly FlowValidationIssue[],
65
+ statuses: Statuses,
66
+ runMode: boolean,
67
+ ) {
68
+ const [snapshot, setSnapshot] = useState(() => project(nodes, issues, statuses, runMode, new Map()));
69
+ if (
70
+ snapshot.nodes !== nodes ||
71
+ snapshot.issues !== issues ||
72
+ snapshot.statuses !== statuses ||
73
+ snapshot.runMode !== runMode
74
+ ) {
75
+ const next = project(nodes, issues, statuses, runMode, snapshot.cache);
76
+ setSnapshot(next);
77
+ return next.displayed;
78
+ }
79
+ return snapshot.displayed;
80
+ }
@@ -0,0 +1,120 @@
1
+ import { useState } from 'react';
2
+ import { NodeCard, type FlowNodeRenderer } from '@gntik-ai/flow';
3
+ import { Button } from '@gntik-ai/ui';
4
+ import { FlowBuilder, type FlowConnection, type FlowNode, type FlowTone } from '../FlowBuilder';
5
+
6
+ const nodeTypes: FlowNodeRenderer[] = [
7
+ {
8
+ type: 'branch',
9
+ handles: [
10
+ { id: 'input', kind: 'target' },
11
+ { id: 'arm-0', kind: 'source' },
12
+ { id: 'arm-1', kind: 'source' },
13
+ ],
14
+ render: ({ data, selected }) => <NodeCard title={data.title} selected={selected} />,
15
+ },
16
+ {
17
+ type: 'parallel',
18
+ handles: [
19
+ { id: 'input', kind: 'target' },
20
+ { id: 'branch', kind: 'source' },
21
+ ],
22
+ render: ({ data, selected }) => <NodeCard title={data.title} selected={selected} />,
23
+ },
24
+ {
25
+ type: 'sequence',
26
+ handles: [
27
+ { id: 'input', kind: 'target' },
28
+ { id: 'next', kind: 'source' },
29
+ ],
30
+ render: ({ data, selected }) => <NodeCard title={data.title} selected={selected} />,
31
+ },
32
+ ];
33
+ const nodes: FlowNode<string>[] = [
34
+ {
35
+ id: 'branch',
36
+ type: 'branch',
37
+ position: { x: 0, y: 64 },
38
+ data: { title: 'Branch' },
39
+ },
40
+ {
41
+ id: 'parallel',
42
+ type: 'parallel',
43
+ position: { x: 256, y: 0 },
44
+ data: { title: 'Parallel' },
45
+ },
46
+ {
47
+ id: 'sequence',
48
+ type: 'sequence',
49
+ position: { x: 256, y: 160 },
50
+ data: { title: 'Sequence' },
51
+ },
52
+ ];
53
+
54
+ function mapEdge(c: FlowConnection) {
55
+ if (c.sourceType === 'branch')
56
+ return {
57
+ type: 'smoothstep',
58
+ data: {
59
+ kind: 'branch-arm',
60
+ armIndex: Number(c.sourceHandle?.split('-')[1]),
61
+ },
62
+ };
63
+ if (c.sourceType === 'parallel') return { type: 'smoothstep', data: { kind: 'parallel', branch: 'first' } };
64
+ return { type: 'smoothstep', data: { kind: 'sequence', step: 1 } };
65
+ }
66
+
67
+ /** Named branch arms, parallel and sequence handles. Edge payloads remain consumer-owned. */
68
+ export default function CustomFlowExample() {
69
+ const [rejected, setRejected] = useState('');
70
+ return (
71
+ <>
72
+ <FlowBuilder
73
+ nodes={nodes}
74
+ edges={[]}
75
+ nodeTypes={nodeTypes}
76
+ onConnectEdge={mapEdge}
77
+ isValidConnection={(c) =>
78
+ c.source !== c.target || {
79
+ code: 'self-loop',
80
+ nodeId: c.source,
81
+ message: 'Choose another target node.',
82
+ }
83
+ }
84
+ onConnectionRejected={(issue) => setRejected(issue.message)}
85
+ validationIssues={[
86
+ {
87
+ code: 'required',
88
+ nodeId: 'branch',
89
+ message: 'Choose a branch condition.',
90
+ },
91
+ { code: 'terminal', message: 'Add a terminal step.' },
92
+ ]}
93
+ />
94
+ <p aria-live="polite">{rejected}</p>
95
+ </>
96
+ );
97
+ }
98
+
99
+ export function RunFlowExample() {
100
+ const [status, setStatus] = useState<Record<string, FlowTone>>({
101
+ branch: 'running',
102
+ parallel: 'paused',
103
+ sequence: 'paused',
104
+ });
105
+ const [selected, setSelected] = useState('');
106
+ return (
107
+ <>
108
+ <FlowBuilder
109
+ nodes={nodes}
110
+ edges={[]}
111
+ nodeTypes={nodeTypes}
112
+ mode="run"
113
+ nodeStatus={status}
114
+ onNodeSelect={setSelected}
115
+ />
116
+ <Button onClick={() => setStatus((s) => ({ ...s, branch: 'done' }))}>Complete branch</Button>
117
+ <p aria-live="polite">{selected && `Selected node: ${selected}`}</p>
118
+ </>
119
+ );
120
+ }
@@ -0,0 +1,37 @@
1
+ import type { FlowNodeRenderer, FlowTone } from '@gntik-ai/flow';
2
+ import type { FlowEdge } from './fixtures';
3
+
4
+ /** Connection plus node type identifiers, for consumer edge mapping and validation. */
5
+ export interface FlowConnection {
6
+ source: string;
7
+ target: string;
8
+ sourceHandle: string | null;
9
+ targetHandle: string | null;
10
+ sourceType: string;
11
+ targetType: string;
12
+ }
13
+
14
+ export interface FlowValidationIssue {
15
+ code: string;
16
+ nodeId?: string;
17
+ message: string;
18
+ }
19
+
20
+ export interface FlowConnectionRejection extends FlowValidationIssue {
21
+ nodeId: string;
22
+ }
23
+
24
+ export interface FlowBuilderExtensions {
25
+ mode?: 'edit' | 'run';
26
+ nodeStatus?: Readonly<Record<string, FlowTone | undefined>>;
27
+ onNodeSelect?: (id: string) => void;
28
+ validationIssues?: readonly FlowValidationIssue[];
29
+ nodeTypes?: readonly FlowNodeRenderer[];
30
+ /** Return edge kind and payload; endpoints always come from the connection. */
31
+ onConnectEdge?: (connection: FlowConnection) => Pick<FlowEdge, 'type' | 'data'>;
32
+ /** Return true to accept, false for a generic rejection, or an issue for a specific reason. Pure: called during dragging. */
33
+ isValidConnection?: (connection: FlowConnection) => boolean | FlowValidationIssue;
34
+ onConnectionRejected?: (issue: FlowConnectionRejection) => void;
35
+ /** Called only after an accepted connection has been added. */
36
+ onConnect?: (connection: FlowConnection) => void;
37
+ }