@gntik-ai/blocks 0.0.0-stage → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (397) hide show
  1. package/dist/index.d.ts +3626 -0
  2. package/dist/index.js +15417 -0
  3. package/dist/index.js.map +1 -0
  4. package/package.json +62 -3
  5. package/src/ai/AssistantCardGrid/AssistantCardGrid.test.tsx +32 -0
  6. package/src/ai/AssistantCardGrid/AssistantCardGrid.tsx +98 -0
  7. package/src/ai/AssistantCardGrid/block.meta.ts +10 -0
  8. package/src/ai/AssistantCardGrid/fixtures.ts +72 -0
  9. package/src/ai/EvaluationScorecard/EvaluationScorecard.test.tsx +39 -0
  10. package/src/ai/EvaluationScorecard/EvaluationScorecard.tsx +233 -0
  11. package/src/ai/EvaluationScorecard/block.meta.ts +10 -0
  12. package/src/ai/EvaluationScorecard/fixtures.ts +79 -0
  13. package/src/ai/ModelKeysList/ModelKeysList.test.tsx +44 -0
  14. package/src/ai/ModelKeysList/ModelKeysList.tsx +203 -0
  15. package/src/ai/ModelKeysList/block.meta.ts +10 -0
  16. package/src/ai/ModelKeysList/fixtures.ts +44 -0
  17. package/src/ai/PromptEditor/PromptEditor.test.tsx +97 -0
  18. package/src/ai/PromptEditor/PromptEditor.tsx +202 -0
  19. package/src/ai/PromptEditor/block.meta.ts +10 -0
  20. package/src/ai/PromptEditor/examples/controlled-variables.tsx +23 -0
  21. package/src/ai/PromptEditor/fixtures.ts +12 -0
  22. package/src/ai/TokenCostCard/TokenCostCard.test.tsx +36 -0
  23. package/src/ai/TokenCostCard/TokenCostCard.tsx +130 -0
  24. package/src/ai/TokenCostCard/block.meta.ts +9 -0
  25. package/src/ai/TokenCostCard/fixtures.ts +18 -0
  26. package/src/ai/TraceWaterfall/TraceWaterfall.test.tsx +119 -0
  27. package/src/ai/TraceWaterfall/TraceWaterfall.tsx +317 -0
  28. package/src/ai/TraceWaterfall/block.meta.ts +10 -0
  29. package/src/ai/TraceWaterfall/examples/no-details.tsx +6 -0
  30. package/src/ai/TraceWaterfall/examples/running.tsx +16 -0
  31. package/src/ai/TraceWaterfall/fixtures.ts +133 -0
  32. package/src/ai/TraceWaterfall/span-details.tsx +51 -0
  33. package/src/ai/index.ts +13 -0
  34. package/src/auth/ForgotPasswordForm/ForgotPasswordForm.test.tsx +209 -0
  35. package/src/auth/ForgotPasswordForm/ForgotPasswordForm.tsx +211 -0
  36. package/src/auth/ForgotPasswordForm/block.meta.ts +10 -0
  37. package/src/auth/ForgotPasswordForm/examples/UsernameOrEmail.tsx +24 -0
  38. package/src/auth/MfaChallenge/MfaChallenge.test.tsx +35 -0
  39. package/src/auth/MfaChallenge/MfaChallenge.tsx +170 -0
  40. package/src/auth/MfaChallenge/block.meta.ts +9 -0
  41. package/src/auth/ResetPasswordForm/ResetPasswordForm.test.tsx +27 -0
  42. package/src/auth/ResetPasswordForm/ResetPasswordForm.tsx +100 -0
  43. package/src/auth/ResetPasswordForm/block.meta.ts +10 -0
  44. package/src/auth/SignInForm/SignInForm.test.tsx +222 -0
  45. package/src/auth/SignInForm/SignInForm.tsx +226 -0
  46. package/src/auth/SignInForm/block.meta.ts +22 -0
  47. package/src/auth/SignInForm/examples/Username.tsx +26 -0
  48. package/src/auth/SignUpForm/SignUpForm.configured.test.tsx +316 -0
  49. package/src/auth/SignUpForm/SignUpForm.test.tsx +33 -0
  50. package/src/auth/SignUpForm/SignUpForm.tsx +298 -0
  51. package/src/auth/SignUpForm/block.meta.ts +22 -0
  52. package/src/auth/SignUpForm/examples/CustomFields.tsx +56 -0
  53. package/src/auth/SignUpForm/examples/Error.tsx +5 -0
  54. package/src/auth/SignUpForm/examples/Loading.tsx +6 -0
  55. package/src/auth/SignUpForm/examples/NoTerms.tsx +6 -0
  56. package/src/auth/SignUpForm/examples/Submitting.tsx +5 -0
  57. package/src/auth/SignUpForm/examples/Unavailable.tsx +15 -0
  58. package/src/auth/SsoButtons/SsoButtons.test.tsx +23 -0
  59. package/src/auth/SsoButtons/SsoButtons.tsx +52 -0
  60. package/src/auth/SsoButtons/block.meta.ts +9 -0
  61. package/src/auth/SsoButtons/fixtures.ts +15 -0
  62. package/src/auth/index.ts +16 -0
  63. package/src/auth/shared.tsx +147 -0
  64. package/src/billing/CostBreakdown/CostBreakdown.test.tsx +24 -0
  65. package/src/billing/CostBreakdown/CostBreakdown.tsx +95 -0
  66. package/src/billing/CostBreakdown/block.meta.ts +10 -0
  67. package/src/billing/CostBreakdown/fixtures.ts +9 -0
  68. package/src/billing/InvoiceTable/InvoiceTable.test.tsx +29 -0
  69. package/src/billing/InvoiceTable/InvoiceTable.tsx +130 -0
  70. package/src/billing/InvoiceTable/block.meta.ts +10 -0
  71. package/src/billing/InvoiceTable/fixtures.ts +38 -0
  72. package/src/billing/PaymentMethodCard/PaymentMethodCard.test.tsx +28 -0
  73. package/src/billing/PaymentMethodCard/PaymentMethodCard.tsx +103 -0
  74. package/src/billing/PaymentMethodCard/block.meta.ts +10 -0
  75. package/src/billing/PaymentMethodCard/fixtures.ts +12 -0
  76. package/src/billing/PlanCard/PlanCard.baseline.test.tsx +26 -0
  77. package/src/billing/PlanCard/PlanCard.summary.test.tsx +227 -0
  78. package/src/billing/PlanCard/PlanCard.test.tsx +27 -0
  79. package/src/billing/PlanCard/PlanCard.tsx +140 -0
  80. package/src/billing/PlanCard/PlanSummary.tsx +101 -0
  81. package/src/billing/PlanCard/README.md +23 -0
  82. package/src/billing/PlanCard/__snapshots__/PlanCard.baseline.test.tsx.snap +5 -0
  83. package/src/billing/PlanCard/block.meta.ts +60 -0
  84. package/src/billing/PlanCard/examples/SummaryActionsOnly.tsx +12 -0
  85. package/src/billing/PlanCard/examples/SummaryFeaturesOnly.tsx +14 -0
  86. package/src/billing/PlanCard/examples/SummaryFull.tsx +28 -0
  87. package/src/billing/PlanCard/examples/SummaryMinimal.tsx +5 -0
  88. package/src/billing/PlanCard/examples/SummaryPriceOnly.tsx +15 -0
  89. package/src/billing/PlanCard/examples/SummaryRenewalOnly.tsx +14 -0
  90. package/src/billing/PlanCard/examples/SummaryUnassigned.tsx +5 -0
  91. package/src/billing/PlanCard/examples/SummaryUnassignedNoLabel.tsx +5 -0
  92. package/src/billing/PlanCard/examples/SummaryZeroPrice.tsx +15 -0
  93. package/src/billing/PlanCard/fixtures.ts +19 -0
  94. package/src/billing/PlanDefinitionCard/PlanDefinitionCard.test.tsx +111 -0
  95. package/src/billing/PlanDefinitionCard/PlanDefinitionCard.tsx +86 -0
  96. package/src/billing/PlanDefinitionCard/README.md +29 -0
  97. package/src/billing/PlanDefinitionCard/block.meta.ts +46 -0
  98. package/src/billing/PlanDefinitionCard/examples/Compact.tsx +23 -0
  99. package/src/billing/PlanDefinitionCard/examples/Full.tsx +23 -0
  100. package/src/billing/PlanDefinitionCard/fixtures.ts +12 -0
  101. package/src/billing/QuotaMeters/QuotaMeters.test.tsx +23 -0
  102. package/src/billing/QuotaMeters/QuotaMeters.tsx +92 -0
  103. package/src/billing/QuotaMeters/block.meta.ts +10 -0
  104. package/src/billing/QuotaMeters/fixtures.ts +9 -0
  105. package/src/billing/SpendVsBudget/SpendVsBudget.test.tsx +24 -0
  106. package/src/billing/SpendVsBudget/SpendVsBudget.tsx +105 -0
  107. package/src/billing/SpendVsBudget/block.meta.ts +10 -0
  108. package/src/billing/SpendVsBudget/fixtures.ts +18 -0
  109. package/src/billing/format.ts +42 -0
  110. package/src/billing/index.ts +21 -0
  111. package/src/builders/CodePanel/CodePanel.test.tsx +280 -0
  112. package/src/builders/CodePanel/CodePanel.tsx +239 -0
  113. package/src/builders/CodePanel/block.meta.ts +10 -0
  114. package/src/builders/CodePanel/examples/controlled.tsx +15 -0
  115. package/src/builders/CodePanel/examples/diagnostics.tsx +40 -0
  116. package/src/builders/CodePanel/fixtures.ts +95 -0
  117. package/src/builders/CodePanel/marker-store.ts +90 -0
  118. package/src/builders/CodePanel/markers.ts +62 -0
  119. package/src/builders/FlowBuilder/FlowBuilder.test.tsx +480 -0
  120. package/src/builders/FlowBuilder/FlowBuilder.tsx +343 -0
  121. package/src/builders/FlowBuilder/block.meta.ts +20 -0
  122. package/src/builders/FlowBuilder/connections.ts +67 -0
  123. package/src/builders/FlowBuilder/decorated-nodes.ts +80 -0
  124. package/src/builders/FlowBuilder/examples/extensions.tsx +120 -0
  125. package/src/builders/FlowBuilder/extensions.ts +37 -0
  126. package/src/builders/FlowBuilder/fixtures.ts +80 -0
  127. package/src/builders/FlowBuilder/parts.tsx +196 -0
  128. package/src/builders/FlowBuilder/problems.tsx +40 -0
  129. package/src/builders/LogViewer/LogViewer.test.tsx +97 -0
  130. package/src/builders/LogViewer/LogViewer.tsx +253 -0
  131. package/src/builders/LogViewer/block.meta.ts +10 -0
  132. package/src/builders/LogViewer/fixtures.ts +49 -0
  133. package/src/builders/index.ts +15 -0
  134. package/src/data-display/ActivityFeed/ActivityFeed.test.tsx +62 -0
  135. package/src/data-display/ActivityFeed/ActivityFeed.tsx +118 -0
  136. package/src/data-display/ActivityFeed/block.meta.ts +10 -0
  137. package/src/data-display/ActivityFeed/fixtures.ts +29 -0
  138. package/src/data-display/ChartCard/ChartCard.ranges.test.tsx +282 -0
  139. package/src/data-display/ChartCard/ChartCard.test.tsx +54 -0
  140. package/src/data-display/ChartCard/ChartCard.tsx +218 -0
  141. package/src/data-display/ChartCard/block.meta.ts +10 -0
  142. package/src/data-display/ChartCard/examples/custom-ranges.tsx +37 -0
  143. package/src/data-display/ChartCard/examples/heatmap.tsx +20 -0
  144. package/src/data-display/ChartCard/examples/toolbar-owned-range.tsx +34 -0
  145. package/src/data-display/ChartCard/fixtures.ts +82 -0
  146. package/src/data-display/CommentThread/CommentThread.test.tsx +29 -0
  147. package/src/data-display/CommentThread/CommentThread.tsx +120 -0
  148. package/src/data-display/CommentThread/block.meta.ts +10 -0
  149. package/src/data-display/CommentThread/fixtures.ts +28 -0
  150. package/src/data-display/DescriptionListCard/DescriptionListCard.test.tsx +28 -0
  151. package/src/data-display/DescriptionListCard/DescriptionListCard.tsx +120 -0
  152. package/src/data-display/DescriptionListCard/block.meta.ts +9 -0
  153. package/src/data-display/DescriptionListCard/fixtures.ts +12 -0
  154. package/src/data-display/KpiRow/KpiRow.test.tsx +29 -0
  155. package/src/data-display/KpiRow/KpiRow.tsx +101 -0
  156. package/src/data-display/KpiRow/block.meta.ts +10 -0
  157. package/src/data-display/KpiRow/fixtures.ts +34 -0
  158. package/src/data-display/ResourceCardGrid/ResourceCardGrid.test.tsx +22 -0
  159. package/src/data-display/ResourceCardGrid/ResourceCardGrid.tsx +98 -0
  160. package/src/data-display/ResourceCardGrid/block.meta.ts +10 -0
  161. package/src/data-display/ResourceCardGrid/fixtures.ts +72 -0
  162. package/src/data-display/StatCard/StatCard.test.tsx +30 -0
  163. package/src/data-display/StatCard/StatCard.tsx +138 -0
  164. package/src/data-display/StatCard/block.meta.ts +10 -0
  165. package/src/data-display/StatCard/fixtures.ts +26 -0
  166. package/src/data-display/StatusTimeline/StatusTimeline.test.tsx +153 -0
  167. package/src/data-display/StatusTimeline/StatusTimeline.tsx +114 -0
  168. package/src/data-display/StatusTimeline/block.meta.ts +10 -0
  169. package/src/data-display/StatusTimeline/examples/Empty.tsx +6 -0
  170. package/src/data-display/StatusTimeline/fixtures.ts +62 -0
  171. package/src/data-display/index.ts +34 -0
  172. package/src/data-display/shared/TrendDelta.tsx +60 -0
  173. package/src/feedback/ConfirmDestructive/ConfirmDestructive.test.tsx +47 -0
  174. package/src/feedback/ConfirmDestructive/ConfirmDestructive.tsx +145 -0
  175. package/src/feedback/ConfirmDestructive/block.meta.ts +10 -0
  176. package/src/feedback/EmptyStates/EmptyStates.test.tsx +48 -0
  177. package/src/feedback/EmptyStates/EmptyStates.tsx +191 -0
  178. package/src/feedback/EmptyStates/block.meta.ts +10 -0
  179. package/src/feedback/EmptyStates/fixtures.ts +6 -0
  180. package/src/feedback/ErrorPanel/ErrorPanel.test.tsx +37 -0
  181. package/src/feedback/ErrorPanel/ErrorPanel.tsx +146 -0
  182. package/src/feedback/ErrorPanel/block.meta.ts +10 -0
  183. package/src/feedback/InlineCallout/InlineCallout.test.tsx +33 -0
  184. package/src/feedback/InlineCallout/InlineCallout.tsx +81 -0
  185. package/src/feedback/InlineCallout/block.meta.ts +9 -0
  186. package/src/feedback/Skeletons/Skeletons.test.tsx +25 -0
  187. package/src/feedback/Skeletons/Skeletons.tsx +142 -0
  188. package/src/feedback/Skeletons/block.meta.ts +10 -0
  189. package/src/feedback/SystemBanner/SystemBanner.test.tsx +29 -0
  190. package/src/feedback/SystemBanner/SystemBanner.tsx +105 -0
  191. package/src/feedback/SystemBanner/block.meta.ts +9 -0
  192. package/src/feedback/index.ts +14 -0
  193. package/src/feedback/types.ts +13 -0
  194. package/src/forms/CreateApiKeyDialog/CreateApiKeyDialog.test.tsx +36 -0
  195. package/src/forms/CreateApiKeyDialog/CreateApiKeyDialog.tsx +197 -0
  196. package/src/forms/CreateApiKeyDialog/block.meta.ts +10 -0
  197. package/src/forms/CreateApiKeyDialog/fixtures.ts +31 -0
  198. package/src/forms/CredentialDisclosureDialog/CredentialDisclosureDialog.test.tsx +339 -0
  199. package/src/forms/CredentialDisclosureDialog/CredentialDisclosureDialog.tsx +193 -0
  200. package/src/forms/CredentialDisclosureDialog/block.meta.ts +10 -0
  201. package/src/forms/CredentialDisclosureDialog/examples/Fresh.tsx +32 -0
  202. package/src/forms/CredentialDisclosureDialog/examples/ResponseGating.tsx +69 -0
  203. package/src/forms/CredentialDisclosureDialog/examples/Stored.tsx +32 -0
  204. package/src/forms/CredentialDisclosureDialog/fixtures.ts +14 -0
  205. package/src/forms/DangerZone/ConfirmDestructiveDialog.tsx +101 -0
  206. package/src/forms/DangerZone/DangerZone.test.tsx +28 -0
  207. package/src/forms/DangerZone/DangerZone.tsx +86 -0
  208. package/src/forms/DangerZone/block.meta.ts +10 -0
  209. package/src/forms/DangerZone/fixtures.ts +36 -0
  210. package/src/forms/DateRangeFilter/DateRangeFilter.test.tsx +26 -0
  211. package/src/forms/DateRangeFilter/DateRangeFilter.tsx +83 -0
  212. package/src/forms/DateRangeFilter/block.meta.ts +9 -0
  213. package/src/forms/DialogWizard/DialogWizard.test.tsx +95 -0
  214. package/src/forms/DialogWizard/DialogWizard.tsx +39 -0
  215. package/src/forms/DialogWizard/block.meta.ts +10 -0
  216. package/src/forms/DialogWizard/examples/CreateResource.tsx +65 -0
  217. package/src/forms/DialogWizard/fixtures.ts +20 -0
  218. package/src/forms/FileUploadPanel/FileUploadPanel.test.tsx +43 -0
  219. package/src/forms/FileUploadPanel/FileUploadPanel.tsx +244 -0
  220. package/src/forms/FileUploadPanel/block.meta.ts +10 -0
  221. package/src/forms/FileUploadPanel/fixtures.ts +66 -0
  222. package/src/forms/FormSection/FormSection.test.tsx +34 -0
  223. package/src/forms/FormSection/FormSection.tsx +77 -0
  224. package/src/forms/FormSection/block.meta.ts +9 -0
  225. package/src/forms/InviteMembersDialog/InviteMembersDialog.test.tsx +37 -0
  226. package/src/forms/InviteMembersDialog/InviteMembersDialog.tsx +183 -0
  227. package/src/forms/InviteMembersDialog/block.meta.ts +10 -0
  228. package/src/forms/InviteMembersDialog/fixtures.ts +14 -0
  229. package/src/forms/SettingsRow/SettingsRow.test.tsx +50 -0
  230. package/src/forms/SettingsRow/SettingsRow.tsx +74 -0
  231. package/src/forms/SettingsRow/block.meta.ts +9 -0
  232. package/src/forms/ValidationSummary/ValidationSummary.test.tsx +46 -0
  233. package/src/forms/ValidationSummary/ValidationSummary.tsx +75 -0
  234. package/src/forms/ValidationSummary/block.meta.ts +9 -0
  235. package/src/forms/ValidationSummary/fixtures.ts +13 -0
  236. package/src/forms/index.ts +27 -0
  237. package/src/index.ts +14 -0
  238. package/src/marketing/ComparisonTable/ComparisonTable.test.tsx +69 -0
  239. package/src/marketing/ComparisonTable/ComparisonTable.tsx +245 -0
  240. package/src/marketing/ComparisonTable/block.meta.ts +10 -0
  241. package/src/marketing/ComparisonTable/examples/options.tsx +48 -0
  242. package/src/marketing/ComparisonTable/fixtures.ts +83 -0
  243. package/src/marketing/Faq/Faq.test.tsx +25 -0
  244. package/src/marketing/Faq/Faq.tsx +68 -0
  245. package/src/marketing/Faq/block.meta.ts +10 -0
  246. package/src/marketing/Faq/fixtures.ts +32 -0
  247. package/src/marketing/FeatureGrid/FeatureGrid.test.tsx +19 -0
  248. package/src/marketing/FeatureGrid/FeatureGrid.tsx +79 -0
  249. package/src/marketing/FeatureGrid/block.meta.ts +10 -0
  250. package/src/marketing/FeatureGrid/fixtures.ts +36 -0
  251. package/src/marketing/Hero/Hero.test.tsx +33 -0
  252. package/src/marketing/Hero/Hero.tsx +107 -0
  253. package/src/marketing/Hero/block.meta.ts +10 -0
  254. package/src/marketing/PricingTable/PricingTable.test.tsx +92 -0
  255. package/src/marketing/PricingTable/PricingTable.tsx +323 -0
  256. package/src/marketing/PricingTable/block.meta.ts +10 -0
  257. package/src/marketing/PricingTable/fixtures.ts +34 -0
  258. package/src/marketing/actions.tsx +40 -0
  259. package/src/marketing/index.ts +15 -0
  260. package/src/meta.ts +25 -0
  261. package/src/page-chrome/BulkActionBar/BulkActionBar.test.tsx +34 -0
  262. package/src/page-chrome/BulkActionBar/BulkActionBar.tsx +76 -0
  263. package/src/page-chrome/BulkActionBar/block.meta.ts +9 -0
  264. package/src/page-chrome/PageHeader/PageHeader.test.tsx +68 -0
  265. package/src/page-chrome/PageHeader/PageHeader.tsx +152 -0
  266. package/src/page-chrome/PageHeader/block.meta.ts +10 -0
  267. package/src/page-chrome/PageHeader/fixtures.ts +29 -0
  268. package/src/page-chrome/PageToolbar/PageToolbar.test.tsx +38 -0
  269. package/src/page-chrome/PageToolbar/PageToolbar.tsx +138 -0
  270. package/src/page-chrome/PageToolbar/block.meta.ts +10 -0
  271. package/src/page-chrome/PageToolbar/fixtures.ts +8 -0
  272. package/src/page-chrome/SectionHeader/SectionHeader.test.tsx +28 -0
  273. package/src/page-chrome/SectionHeader/SectionHeader.tsx +73 -0
  274. package/src/page-chrome/SectionHeader/block.meta.ts +9 -0
  275. package/src/page-chrome/StickyActionBar/StickyActionBar.test.tsx +29 -0
  276. package/src/page-chrome/StickyActionBar/StickyActionBar.tsx +66 -0
  277. package/src/page-chrome/StickyActionBar/block.meta.ts +9 -0
  278. package/src/page-chrome/index.ts +13 -0
  279. package/src/page-chrome/types.ts +25 -0
  280. package/src/scheduling/AgendaList/AgendaList.test.tsx +28 -0
  281. package/src/scheduling/AgendaList/AgendaList.tsx +165 -0
  282. package/src/scheduling/AgendaList/block.meta.ts +10 -0
  283. package/src/scheduling/MonthCalendar/MonthCalendar.test.tsx +32 -0
  284. package/src/scheduling/MonthCalendar/MonthCalendar.tsx +223 -0
  285. package/src/scheduling/MonthCalendar/block.meta.ts +10 -0
  286. package/src/scheduling/TimeSlotPicker/TimeSlotPicker.test.tsx +26 -0
  287. package/src/scheduling/TimeSlotPicker/TimeSlotPicker.tsx +115 -0
  288. package/src/scheduling/TimeSlotPicker/block.meta.ts +10 -0
  289. package/src/scheduling/TimeSlotPicker/fixtures.ts +10 -0
  290. package/src/scheduling/WeekCalendar/WeekCalendar.test.tsx +27 -0
  291. package/src/scheduling/WeekCalendar/WeekCalendar.tsx +274 -0
  292. package/src/scheduling/WeekCalendar/block.meta.ts +10 -0
  293. package/src/scheduling/index.ts +11 -0
  294. package/src/scheduling/shared/CalendarHeader.tsx +54 -0
  295. package/src/scheduling/shared/EventTile.tsx +21 -0
  296. package/src/scheduling/shared/dates.ts +98 -0
  297. package/src/scheduling/shared/events.ts +90 -0
  298. package/src/scheduling/shared/fixtures.ts +54 -0
  299. package/src/shell/AppSidebar/AppSidebar.test.tsx +38 -0
  300. package/src/shell/AppSidebar/AppSidebar.tsx +226 -0
  301. package/src/shell/AppSidebar/block.meta.ts +10 -0
  302. package/src/shell/AppSidebar/fixtures.ts +58 -0
  303. package/src/shell/AppTopbar/AppTopbar.test.tsx +41 -0
  304. package/src/shell/AppTopbar/AppTopbar.tsx +82 -0
  305. package/src/shell/AppTopbar/block.meta.ts +19 -0
  306. package/src/shell/AppTopbar/fixtures.ts +22 -0
  307. package/src/shell/EnvironmentBadge/EnvironmentBadge.test.tsx +22 -0
  308. package/src/shell/EnvironmentBadge/EnvironmentBadge.tsx +55 -0
  309. package/src/shell/EnvironmentBadge/block.meta.ts +10 -0
  310. package/src/shell/GlobalSearch/GlobalSearch.test.tsx +28 -0
  311. package/src/shell/GlobalSearch/GlobalSearch.tsx +80 -0
  312. package/src/shell/GlobalSearch/block.meta.ts +10 -0
  313. package/src/shell/GlobalSearch/fixtures.ts +64 -0
  314. package/src/shell/index.ts +11 -0
  315. package/src/styles.css +2 -0
  316. package/src/tables/DataTable/DataTable.edit-views.test.tsx +249 -0
  317. package/src/tables/DataTable/DataTable.labels.test.tsx +291 -0
  318. package/src/tables/DataTable/DataTable.layout.test.tsx +188 -0
  319. package/src/tables/DataTable/DataTable.overflow-labels.test.tsx +59 -0
  320. package/src/tables/DataTable/DataTable.single-select.test.tsx +501 -0
  321. package/src/tables/DataTable/DataTable.test.tsx +268 -0
  322. package/src/tables/DataTable/DataTable.tsx +531 -0
  323. package/src/tables/DataTable/block.meta.ts +129 -0
  324. package/src/tables/DataTable/column-menu.tsx +112 -0
  325. package/src/tables/DataTable/column-sizing.ts +166 -0
  326. package/src/tables/DataTable/data-table-body.tsx +195 -0
  327. package/src/tables/DataTable/data-table-props.ts +159 -0
  328. package/src/tables/DataTable/data-table-row.tsx +231 -0
  329. package/src/tables/DataTable/data-table-utils.ts +195 -0
  330. package/src/tables/DataTable/density-toggle.tsx +33 -0
  331. package/src/tables/DataTable/editable-cell.tsx +287 -0
  332. package/src/tables/DataTable/examples/editable.tsx +71 -0
  333. package/src/tables/DataTable/examples/grouped.tsx +18 -0
  334. package/src/tables/DataTable/examples/resizable-pinned.tsx +20 -0
  335. package/src/tables/DataTable/examples/saved-views.tsx +61 -0
  336. package/src/tables/DataTable/examples/single-selection.tsx +55 -0
  337. package/src/tables/DataTable/examples/virtualized.tsx +20 -0
  338. package/src/tables/DataTable/fixtures.ts +119 -0
  339. package/src/tables/DataTable/header-cell.tsx +90 -0
  340. package/src/tables/DataTable/labels.ts +54 -0
  341. package/src/tables/DataTable/resize-handle.tsx +91 -0
  342. package/src/tables/DataTable/single-selection.tsx +149 -0
  343. package/src/tables/DataTable/use-table-views.ts +79 -0
  344. package/src/tables/DataTable/use-virtual-rows.ts +83 -0
  345. package/src/tables/DataTable/views.tsx +193 -0
  346. package/src/tables/FilterBar/FilterBar.labels.test.tsx +69 -0
  347. package/src/tables/FilterBar/FilterBar.test.tsx +43 -0
  348. package/src/tables/FilterBar/FilterBar.tsx +232 -0
  349. package/src/tables/FilterBar/block.meta.ts +32 -0
  350. package/src/tables/FilterBar/fixtures.ts +32 -0
  351. package/src/tables/InlineEditRow/InlineEditRow.test.tsx +41 -0
  352. package/src/tables/InlineEditRow/InlineEditRow.tsx +215 -0
  353. package/src/tables/InlineEditRow/block.meta.ts +10 -0
  354. package/src/tables/InlineEditRow/fixtures.ts +28 -0
  355. package/src/tables/PaginationFooter/PaginationFooter.labels.test.tsx +57 -0
  356. package/src/tables/PaginationFooter/PaginationFooter.test.tsx +29 -0
  357. package/src/tables/PaginationFooter/PaginationFooter.tsx +118 -0
  358. package/src/tables/PaginationFooter/block.meta.ts +33 -0
  359. package/src/tables/SubordinateList/SubordinateList.test.tsx +351 -0
  360. package/src/tables/SubordinateList/SubordinateList.tsx +140 -0
  361. package/src/tables/SubordinateList/block.meta.ts +10 -0
  362. package/src/tables/SubordinateList/examples/ResourceForm.tsx +129 -0
  363. package/src/tables/SubordinateList/examples/empty.tsx +5 -0
  364. package/src/tables/SubordinateList/examples/error.tsx +5 -0
  365. package/src/tables/SubordinateList/examples/loading.tsx +5 -0
  366. package/src/tables/TruncatedCollectionFooter/TruncatedCollectionFooter.test.tsx +159 -0
  367. package/src/tables/TruncatedCollectionFooter/TruncatedCollectionFooter.tsx +60 -0
  368. package/src/tables/TruncatedCollectionFooter/block.meta.ts +10 -0
  369. package/src/tables/TruncatedCollectionFooter/examples/exact.tsx +6 -0
  370. package/src/tables/TruncatedCollectionFooter/examples/has-more.tsx +12 -0
  371. package/src/tables/TruncatedCollectionFooter/fixtures.ts +2 -0
  372. package/src/tables/index.ts +45 -0
  373. package/src/team/MembersTable/MembersTable.test.tsx +29 -0
  374. package/src/team/MembersTable/MembersTable.tsx +141 -0
  375. package/src/team/MembersTable/block.meta.ts +9 -0
  376. package/src/team/MembersTable/fixtures.ts +19 -0
  377. package/src/team/NotificationMatrix/NotificationMatrix.test.tsx +36 -0
  378. package/src/team/NotificationMatrix/NotificationMatrix.tsx +144 -0
  379. package/src/team/NotificationMatrix/block.meta.ts +9 -0
  380. package/src/team/NotificationMatrix/fixtures.ts +43 -0
  381. package/src/team/PendingInvitations/PendingInvitations.test.tsx +30 -0
  382. package/src/team/PendingInvitations/PendingInvitations.tsx +132 -0
  383. package/src/team/PendingInvitations/block.meta.ts +9 -0
  384. package/src/team/PendingInvitations/fixtures.ts +39 -0
  385. package/src/team/ProfileHeader/ProfileHeader.test.tsx +29 -0
  386. package/src/team/ProfileHeader/ProfileHeader.tsx +106 -0
  387. package/src/team/ProfileHeader/block.meta.ts +10 -0
  388. package/src/team/ProfileHeader/fixtures.ts +28 -0
  389. package/src/team/SessionsDevices/SessionsDevices.test.tsx +30 -0
  390. package/src/team/SessionsDevices/SessionsDevices.tsx +126 -0
  391. package/src/team/SessionsDevices/block.meta.ts +10 -0
  392. package/src/team/SessionsDevices/fixtures.ts +58 -0
  393. package/src/team/index.ts +15 -0
  394. package/src/test/a11y.ts +18 -0
  395. package/src/test/heading-levels.test.tsx +69 -0
  396. package/src/test/setup.ts +36 -0
  397. package/README.md +0 -3
@@ -0,0 +1,65 @@
1
+ import { Button, Field, FieldLabel, Input, Stack } from '@gntik-ai/ui';
2
+ import { useState } from 'react';
3
+ import { DescriptionListCard } from '../../../data-display/DescriptionListCard/DescriptionListCard';
4
+ import { ValidationSummary } from '../../ValidationSummary/ValidationSummary';
5
+ import { DialogWizard } from '../DialogWizard';
6
+ import { dialogWizardCopy, dialogWizardSteps } from '../fixtures';
7
+
8
+ export default function CreateResource() {
9
+ const [open, setOpen] = useState(false);
10
+ const [current, setCurrent] = useState(0);
11
+ const [name, setName] = useState('');
12
+ const [pending, setPending] = useState(false);
13
+ const [created, setCreated] = useState('');
14
+ const valid = name.trim().length > 0;
15
+ const finish = () => {
16
+ if (pending) return;
17
+ setPending(true);
18
+ window.setTimeout(() => {
19
+ setCreated(name);
20
+ setOpen(false);
21
+ setPending(false);
22
+ }, 800);
23
+ };
24
+ return (
25
+ <>
26
+ <Button onClick={() => setOpen(true)}>{dialogWizardCopy.title}</Button>
27
+ <p role="status">{created && `Created ${created}`}</p>
28
+ <DialogWizard
29
+ {...dialogWizardCopy}
30
+ open={open}
31
+ onOpenChange={setOpen}
32
+ steps={dialogWizardSteps}
33
+ current={current}
34
+ onStepChange={setCurrent}
35
+ nextDisabled={current === 0 && !valid}
36
+ onFinish={finish}
37
+ finishPending={pending}
38
+ stepError={
39
+ current === 0 &&
40
+ !valid && (
41
+ <ValidationSummary
42
+ title="A name is required"
43
+ errors={[{ fieldId: 'resource-name', message: 'Enter a resource name' }]}
44
+ />
45
+ )
46
+ }
47
+ reviewStep={
48
+ <DescriptionListCard
49
+ title="Review resource"
50
+ description="Check these answers before creating."
51
+ items={[{ id: 'name', label: 'Name', value: name }]}
52
+ />
53
+ }
54
+ >
55
+ <Stack gap={4}>
56
+ <h2>{dialogWizardSteps[current]?.label}</h2>
57
+ <Field>
58
+ <FieldLabel>Name</FieldLabel>
59
+ <Input id="resource-name" value={name} onValueChange={setName} />
60
+ </Field>
61
+ </Stack>
62
+ </DialogWizard>
63
+ </>
64
+ );
65
+ }
@@ -0,0 +1,20 @@
1
+ /** Example data only; consumers own every label and all step state. */
2
+ export const dialogWizardSteps = [{ label: 'Details' }, { label: 'Configuration' }, { label: 'Review' }];
3
+ export const dialogWizardCopy = {
4
+ title: 'Create resource',
5
+ description: 'Configure and review a new resource.',
6
+ exitLabel: 'Exit',
7
+ cancelLabel: 'Cancel',
8
+ backLabel: 'Back',
9
+ nextLabel: 'Next',
10
+ finishLabel: 'Create resource',
11
+ pendingLabel: 'Creating resource…',
12
+ stepsLabel: 'Setup progress',
13
+ stepsSummaryLabel: 'Setup progress summary',
14
+ exitTitle: 'Leave this setup?',
15
+ exitDescription: 'Your answers stay available when you reopen the flow.',
16
+ exitConfirmLabel: 'Leave',
17
+ exitCancelLabel: 'Stay',
18
+ stepAnnouncement: ({ position, total, label }: { position: number; total: number; label: unknown }) =>
19
+ `Step ${position} of ${total}: ${label}`,
20
+ };
@@ -0,0 +1,43 @@
1
+ import { act, fireEvent, render, screen } from '@testing-library/react';
2
+ import userEvent from '@testing-library/user-event';
3
+ import { expectNoAxeViolations } from '../../test/a11y';
4
+ import { FileUploadPanel } from './FileUploadPanel';
5
+ import { acceptsFile } from './fixtures';
6
+
7
+ describe('FileUploadPanel', () => {
8
+ it('renders the default list', async () => {
9
+ const { container } = render(<FileUploadPanel />);
10
+ expect(screen.getByRole('group', { name: 'Upload files' })).toBeInTheDocument();
11
+ expect(screen.getByRole('progressbar', { name: 'Uploading events-export.jsonl' })).toBeInTheDocument();
12
+ expect(screen.getByText(/File type not accepted/)).toBeInTheDocument();
13
+ await expectNoAxeViolations(container);
14
+ });
15
+
16
+ it('uploads accepted files, rejects the rest and removes items', async () => {
17
+ let finish: () => void = () => {};
18
+ const onUpload = vi.fn((_f: File, onProgress: (p: number) => void) => {
19
+ onProgress(40);
20
+ return new Promise<void>((r) => (finish = r));
21
+ });
22
+ const { container } = render(<FileUploadPanel defaultFiles={[]} onUpload={onUpload} maxSize={1000} />);
23
+ const input = container.querySelector('input[type="file"]') as HTMLInputElement;
24
+ const ok = new File(['a,b'], 'report.csv', { type: 'text/csv' });
25
+ const big = new File(['x'.repeat(2000)], 'big.csv', { type: 'text/csv' });
26
+ const bad = new File(['x'], 'photo.png', { type: 'image/png' });
27
+ fireEvent.change(input, { target: { files: [ok, big, bad] } });
28
+ expect(onUpload).toHaveBeenCalledOnce();
29
+ expect(screen.getByRole('progressbar', { name: 'Uploading report.csv' })).toBeInTheDocument();
30
+ expect(screen.getByText(/Exceeds the 1000 B limit/)).toBeInTheDocument();
31
+ expect(screen.getByText(/File type not accepted/)).toBeInTheDocument();
32
+ await act(async () => finish());
33
+ expect(screen.getByText(/^3 B · uploaded$/)).toBeInTheDocument();
34
+ await userEvent.click(screen.getByRole('button', { name: 'Remove photo.png' }));
35
+ expect(screen.queryByText('photo.png')).not.toBeInTheDocument();
36
+ });
37
+
38
+ it('matches accept lists', () => {
39
+ expect(acceptsFile({ name: 'a.PDF', type: '' }, '.pdf')).toBe(true);
40
+ expect(acceptsFile({ name: 'a.png', type: 'image/png' }, 'image/*')).toBe(true);
41
+ expect(acceptsFile({ name: 'a.png', type: 'image/png' }, '.csv,application/pdf')).toBe(false);
42
+ });
43
+ });
@@ -0,0 +1,244 @@
1
+ import { Button, IconButton, Progress, cn, useI18n } from '@gntik-ai/ui';
2
+ import { CircleAlert, FileText, RotateCcw, Upload, X } from '@gntik-ai/icons';
3
+ import { useId, useRef, useState, type DragEvent } from 'react';
4
+ import { acceptsFile, formatBytes, simulateUpload, uploadItems, type UploadItem } from './fixtures';
5
+
6
+ export type { UploadItem, UploadStatus } from './fixtures';
7
+
8
+ export interface FileUploadPanelProps {
9
+ /** Accepted types, as for `<input accept>` (".csv,.json", "image/*"). */
10
+ accept?: string;
11
+ /** Maximum size per file in bytes. */
12
+ maxSize?: number;
13
+ multiple?: boolean;
14
+ /** Initial list (e.g. files already uploaded). */
15
+ defaultFiles?: UploadItem[];
16
+ /** Uploads one file and reports progress (0–100). Defaults to a demo simulation. */
17
+ onUpload?: (file: File, onProgress: (pct: number) => void) => Promise<void>;
18
+ onRemove?: (item: UploadItem) => void;
19
+ /** Visible label of the panel. */
20
+ label?: string;
21
+ /** Hint under the drop text; defaults to the accepted types and size limit. */
22
+ hint?: string;
23
+ disabled?: boolean;
24
+ className?: string;
25
+ }
26
+
27
+ let uid = 0;
28
+ const nextId = () => `upload-${++uid}`;
29
+
30
+ /**
31
+ * Drop zone with a Browse button, accept and size validation, and a file list with progress,
32
+ * retry and remove. Rejected files stay in the list with the reason.
33
+ */
34
+ export function FileUploadPanel({
35
+ accept = '.csv,.json,.jsonl,.yaml,.pdf',
36
+ maxSize = 25 * 1024 * 1024,
37
+ multiple = true,
38
+ defaultFiles = uploadItems,
39
+ onUpload = simulateUpload,
40
+ onRemove,
41
+ label: labelProp,
42
+ hint,
43
+ disabled = false,
44
+ className,
45
+ }: FileUploadPanelProps) {
46
+ const { t } = useI18n();
47
+ const label = labelProp ?? t('upload.label');
48
+ const id = useId();
49
+ const inputRef = useRef<HTMLInputElement | null>(null);
50
+ const [sources, setSources] = useState<Record<string, File>>({});
51
+ const [items, setItems] = useState<UploadItem[]>(defaultFiles);
52
+ const [dragging, setDragging] = useState(false);
53
+ const [announcement, setAnnouncement] = useState('');
54
+ const hintText =
55
+ hint ??
56
+ t('upload.hint', {
57
+ types: accept
58
+ .split(',')
59
+ .map((a) => a.replace('.', '').toUpperCase())
60
+ .join(' · '),
61
+ size: formatBytes(maxSize),
62
+ });
63
+
64
+ const patch = (itemId: string, next: Partial<UploadItem>) =>
65
+ setItems((list) => list.map((x) => (x.id === itemId ? { ...x, ...next } : x)));
66
+
67
+ const start = (itemId: string, file: File) => {
68
+ patch(itemId, { status: 'uploading', progress: 0, error: undefined });
69
+ onUpload(file, (pct) => patch(itemId, { progress: pct })).then(
70
+ () => {
71
+ patch(itemId, { status: 'done', progress: 100 });
72
+ setAnnouncement(t('upload.done', { name: file.name }));
73
+ },
74
+ (e: unknown) => {
75
+ patch(itemId, { status: 'error', error: e instanceof Error ? e.message : t('upload.failed') });
76
+ setAnnouncement(t('upload.error', { name: file.name }));
77
+ },
78
+ );
79
+ };
80
+
81
+ const addFiles = (list: FileList | File[]) => {
82
+ const files = Array.from(list).slice(0, multiple ? undefined : 1);
83
+ const added: UploadItem[] = [];
84
+ const toStart: Array<[string, File]> = [];
85
+ const kept: Record<string, File> = {};
86
+ for (const file of files) {
87
+ const itemId = nextId();
88
+ const error = !acceptsFile(file, accept)
89
+ ? t('upload.typeRejected')
90
+ : file.size > maxSize
91
+ ? t('upload.tooLarge', { size: formatBytes(maxSize) })
92
+ : undefined;
93
+ added.push({
94
+ id: itemId,
95
+ name: file.name,
96
+ size: file.size,
97
+ progress: 0,
98
+ status: error ? 'error' : 'uploading',
99
+ error,
100
+ });
101
+ if (!error) {
102
+ kept[itemId] = file;
103
+ toStart.push([itemId, file]);
104
+ }
105
+ }
106
+ setItems((current) => (multiple ? [...current, ...added] : added));
107
+ setSources((current) => ({ ...current, ...kept }));
108
+ const rejected = added.filter((a) => a.error).length;
109
+ setAnnouncement(
110
+ [t('upload.added', { count: added.length - rejected }), rejected ? t('upload.rejected', { count: rejected }) : '']
111
+ .filter(Boolean)
112
+ .join(' '),
113
+ );
114
+ for (const [itemId, file] of toStart) start(itemId, file);
115
+ };
116
+
117
+ const remove = (item: UploadItem) => {
118
+ setSources((current) => Object.fromEntries(Object.entries(current).filter(([k]) => k !== item.id)));
119
+ setItems((list) => list.filter((x) => x.id !== item.id));
120
+ setAnnouncement(t('upload.removed', { name: item.name }));
121
+ onRemove?.(item);
122
+ };
123
+
124
+ const retryFor = (itemId: string) => {
125
+ const file = sources[itemId];
126
+ return file ? () => start(itemId, file) : undefined;
127
+ };
128
+
129
+ const onDrop = (e: DragEvent<HTMLDivElement>) => {
130
+ e.preventDefault();
131
+ setDragging(false);
132
+ if (!disabled && e.dataTransfer.files.length) addFiles(e.dataTransfer.files);
133
+ };
134
+
135
+ return (
136
+ <div role="group" aria-labelledby={`${id}-label`} className={cn('w-full', className)}>
137
+ <div id={`${id}-label`} className="mb-2 text-[13px] font-medium text-foreground">
138
+ {label}
139
+ </div>
140
+ <div
141
+ data-dragging={dragging || undefined}
142
+ data-disabled={disabled || undefined}
143
+ onDragOver={(e) => {
144
+ e.preventDefault();
145
+ if (!disabled) setDragging(true);
146
+ }}
147
+ onDragLeave={() => setDragging(false)}
148
+ onDrop={onDrop}
149
+ className={cn(
150
+ 'rounded-xl border-2 border-dashed border-border bg-background/40 px-6 py-8 text-center transition-colors motion-reduce:transition-none',
151
+ 'data-[dragging]:border-primary data-[dragging]:bg-primary/10 data-[disabled]:opacity-60',
152
+ )}
153
+ >
154
+ <div className="mx-auto grid size-12 place-items-center rounded-2xl bg-secondary/70 text-muted-foreground ring-1 ring-border">
155
+ <Upload size={22} aria-hidden />
156
+ </div>
157
+ <p className="mt-4 text-[13.5px] text-foreground">{t('upload.drop')}</p>
158
+ <Button
159
+ variant="secondary"
160
+ size="sm"
161
+ className="mt-2"
162
+ disabled={disabled}
163
+ onClick={() => inputRef.current?.click()}
164
+ aria-describedby={`${id}-hint`}
165
+ >
166
+ {t('upload.browse')}
167
+ </Button>
168
+ <p id={`${id}-hint`} className="mt-3 font-mono text-[11.5px] text-muted-foreground">
169
+ {hintText}
170
+ </p>
171
+ <input
172
+ ref={inputRef}
173
+ type="file"
174
+ hidden
175
+ accept={accept}
176
+ multiple={multiple}
177
+ disabled={disabled}
178
+ onChange={(e) => {
179
+ if (e.target.files?.length) addFiles(e.target.files);
180
+ e.target.value = '';
181
+ }}
182
+ />
183
+ </div>
184
+ {items.length > 0 && (
185
+ <ul aria-label={t('upload.files')} className="mt-4 space-y-2">
186
+ {items.map((item) => (
187
+ <FileRow key={item.id} item={item} onRemove={() => remove(item)} onRetry={retryFor(item.id)} />
188
+ ))}
189
+ </ul>
190
+ )}
191
+ <span role="status" className="sr-only">
192
+ {announcement}
193
+ </span>
194
+ </div>
195
+ );
196
+ }
197
+
198
+ function FileRow({ item, onRemove, onRetry }: { item: UploadItem; onRemove: () => void; onRetry?: () => void }) {
199
+ const { t } = useI18n();
200
+ const err = item.status === 'error';
201
+ const done = item.status === 'done';
202
+ return (
203
+ <li className="flex items-center gap-3 rounded-lg border border-border bg-card px-3.5 py-2.5 shadow-sm">
204
+ <span
205
+ className={cn(
206
+ 'grid size-9 shrink-0 place-items-center rounded-md',
207
+ err
208
+ ? 'bg-destructive/12 text-destructive-chip-text'
209
+ : done
210
+ ? 'bg-primary/14 text-primary-chip-text'
211
+ : 'bg-secondary text-muted-foreground',
212
+ )}
213
+ >
214
+ {err ? <CircleAlert size={17} aria-hidden /> : <FileText size={17} aria-hidden />}
215
+ </span>
216
+ <div className="min-w-0 flex-1">
217
+ <div className="truncate text-[13px] font-medium text-foreground">{item.name}</div>
218
+ {item.status === 'uploading' ? (
219
+ <Progress
220
+ value={item.progress}
221
+ size="sm"
222
+ aria-label={t('upload.uploading', { name: item.name })}
223
+ className="mt-1.5"
224
+ />
225
+ ) : (
226
+ <div className={cn('mt-0.5 font-mono text-[11px]', err ? 'text-destructive-text' : 'text-muted-foreground')}>
227
+ {err
228
+ ? t('upload.notUploaded', { error: item.error ?? t('upload.failed') })
229
+ : t('upload.uploaded', { size: formatBytes(item.size) })}
230
+ </div>
231
+ )}
232
+ </div>
233
+ {item.status === 'uploading' && (
234
+ <span className="shrink-0 font-mono text-[10.5px] text-muted-foreground tabular-nums">
235
+ {Math.round(item.progress)}%
236
+ </span>
237
+ )}
238
+ {err && onRetry && (
239
+ <IconButton icon={RotateCcw} size="sm" label={t('upload.retry', { name: item.name })} onClick={onRetry} />
240
+ )}
241
+ <IconButton icon={X} size="sm" label={t('common.removeItem', { label: item.name })} onClick={onRemove} />
242
+ </li>
243
+ );
244
+ }
@@ -0,0 +1,10 @@
1
+ import type { BlockMeta } from '../../meta';
2
+
3
+ export const meta: BlockMeta = {
4
+ name: 'File upload panel',
5
+ family: 'forms',
6
+ status: 'stable',
7
+ description:
8
+ 'Drag-and-drop zone with a Browse button, type and size validation, and a file list with progress, retry and remove.',
9
+ uses: ['Button', 'IconButton', 'Progress'],
10
+ };
@@ -0,0 +1,66 @@
1
+ export type UploadStatus = 'uploading' | 'done' | 'error';
2
+
3
+ export interface UploadItem {
4
+ id: string;
5
+ name: string;
6
+ /** Size in bytes. */
7
+ size: number;
8
+ /** 0–100. */
9
+ progress: number;
10
+ status: UploadStatus;
11
+ /** Why the file was rejected or failed. */
12
+ error?: string;
13
+ }
14
+
15
+ export const uploadItems: UploadItem[] = [
16
+ { id: 'f1', name: 'service-config.yaml', size: 4_300, progress: 100, status: 'done' },
17
+ { id: 'f2', name: 'events-export.jsonl', size: 1_679_000, progress: 64, status: 'uploading' },
18
+ {
19
+ id: 'f3',
20
+ name: 'db-snapshot.bin',
21
+ size: 2_540_000_000,
22
+ progress: 0,
23
+ status: 'error',
24
+ error: 'File type not accepted',
25
+ },
26
+ ];
27
+
28
+ /** Demo uploader: reports progress in steps and resolves after ~1.5 s. */
29
+ export function simulateUpload(_file: File, onProgress: (pct: number) => void): Promise<void> {
30
+ return new Promise((resolve) => {
31
+ let pct = 0;
32
+ const step = () => {
33
+ pct = Math.min(100, pct + 20);
34
+ onProgress(pct);
35
+ if (pct >= 100) resolve();
36
+ else setTimeout(step, 250);
37
+ };
38
+ setTimeout(step, 250);
39
+ });
40
+ }
41
+
42
+ export function formatBytes(bytes: number) {
43
+ if (bytes < 1024) return `${bytes} B`;
44
+ const units = ['KB', 'MB', 'GB', 'TB'];
45
+ let v = bytes / 1024;
46
+ let i = 0;
47
+ while (v >= 1024 && i < units.length - 1) {
48
+ v /= 1024;
49
+ i++;
50
+ }
51
+ return `${v >= 10 ? Math.round(v) : v.toFixed(1)} ${units[i]}`;
52
+ }
53
+
54
+ /** Matches a file against an `accept` list (".csv", "image/*", "application/pdf"). */
55
+ export function acceptsFile(file: { name: string; type: string }, accept: string | undefined) {
56
+ if (!accept) return true;
57
+ const name = file.name.toLowerCase();
58
+ const type = file.type.toLowerCase();
59
+ return accept
60
+ .split(',')
61
+ .map((a) => a.trim().toLowerCase())
62
+ .filter(Boolean)
63
+ .some((a) =>
64
+ a.startsWith('.') ? name.endsWith(a) : a.endsWith('/*') ? type.startsWith(a.slice(0, -1)) : type === a,
65
+ );
66
+ }
@@ -0,0 +1,34 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import userEvent from '@testing-library/user-event';
3
+ import { Button, Field, FieldLabel, Input } from '@gntik-ai/ui';
4
+ import { expectNoAxeViolations } from '../../test/a11y';
5
+ import { FormSection } from './FormSection';
6
+
7
+ describe('FormSection', () => {
8
+ it('renders the default section as a named region', async () => {
9
+ const { container } = render(<FormSection />);
10
+ expect(screen.getByRole('region', { name: 'General' })).toBeInTheDocument();
11
+ expect(screen.getByLabelText('Project name')).toHaveValue('billing-dashboard');
12
+ await expectNoAxeViolations(container);
13
+ });
14
+
15
+ it('renders custom fields and actions', async () => {
16
+ const onSave = vi.fn();
17
+ render(
18
+ <FormSection
19
+ title="Profile"
20
+ description="How you appear to others."
21
+ actions={<Button onClick={onSave}>Save</Button>}
22
+ >
23
+ <Field>
24
+ <FieldLabel>Display name</FieldLabel>
25
+ <Input />
26
+ </Field>
27
+ </FormSection>,
28
+ );
29
+ await userEvent.type(screen.getByLabelText('Display name'), 'Avery');
30
+ await userEvent.click(screen.getByRole('button', { name: 'Save' }));
31
+ expect(onSave).toHaveBeenCalledOnce();
32
+ expect(screen.getByLabelText('Display name')).toHaveValue('Avery');
33
+ });
34
+ });
@@ -0,0 +1,77 @@
1
+ import { Field, FieldDescription, FieldLabel, Input, Textarea, cn } from '@gntik-ai/ui';
2
+ import { useId, type HTMLAttributes, type ReactNode } from 'react';
3
+
4
+ export interface FormSectionProps extends Omit<HTMLAttributes<HTMLElement>, 'title' | 'className'> {
5
+ /** Section title shown in the left column; also names the section landmark. */
6
+ title?: ReactNode;
7
+ /** Help text under the title. */
8
+ description?: ReactNode;
9
+ /** The fields (right column). Defaults to a sample "General" field set. */
10
+ children?: ReactNode;
11
+ /** Footer row under the fields (e.g. Save / Cancel). */
12
+ actions?: ReactNode;
13
+ /** Heading level that fits the page outline. */
14
+ headingLevel?: 'h2' | 'h3' | 'h4';
15
+ /** Draws a rule above the section (for stacked sections). */
16
+ divided?: boolean;
17
+ className?: string;
18
+ }
19
+
20
+ function SampleFields() {
21
+ return (
22
+ <div className="grid grid-cols-1 gap-4 sm:grid-cols-6">
23
+ <Field className="sm:col-span-4">
24
+ <FieldLabel>Project name</FieldLabel>
25
+ <Input defaultValue="billing-dashboard" />
26
+ <FieldDescription>Lowercase letters, numbers and dashes.</FieldDescription>
27
+ </Field>
28
+ <Field className="sm:col-span-6">
29
+ <FieldLabel>Description</FieldLabel>
30
+ <Textarea rows={3} defaultValue="Usage and invoice reporting for every workspace." />
31
+ </Field>
32
+ </div>
33
+ );
34
+ }
35
+
36
+ /**
37
+ * Two-column form section: title and help on the left, fields on the right. Stacks into a
38
+ * single column on small screens.
39
+ */
40
+ export function FormSection({
41
+ title = 'General',
42
+ description = 'Basic details shown to everyone who can access this project.',
43
+ children,
44
+ actions,
45
+ headingLevel: Heading = 'h2',
46
+ divided = false,
47
+ className,
48
+ ...props
49
+ }: FormSectionProps) {
50
+ const id = useId();
51
+ return (
52
+ <section
53
+ aria-labelledby={title != null ? `${id}-title` : undefined}
54
+ className={cn(
55
+ 'grid grid-cols-1 gap-x-10 gap-y-5 md:grid-cols-3',
56
+ divided && 'border-t border-border pt-8',
57
+ className,
58
+ )}
59
+ {...props}
60
+ >
61
+ <div className="min-w-0">
62
+ {title != null && (
63
+ <Heading id={`${id}-title`} className="text-[14px] font-semibold tracking-tight text-foreground">
64
+ {title}
65
+ </Heading>
66
+ )}
67
+ {description != null && (
68
+ <p className="mt-1 text-[13px] leading-6 text-pretty text-muted-foreground">{description}</p>
69
+ )}
70
+ </div>
71
+ <div className="min-w-0 md:col-span-2">
72
+ {children ?? <SampleFields />}
73
+ {actions != null && <div className="mt-6 flex flex-wrap items-center justify-end gap-2.5">{actions}</div>}
74
+ </div>
75
+ </section>
76
+ );
77
+ }
@@ -0,0 +1,9 @@
1
+ import type { BlockMeta } from '../../meta';
2
+
3
+ export const meta: BlockMeta = {
4
+ name: 'Form section',
5
+ family: 'forms',
6
+ status: 'stable',
7
+ description: 'Two-column settings section: title and help on the left, fields on the right; stacks on small screens.',
8
+ uses: ['Field', 'Input', 'Textarea'],
9
+ };
@@ -0,0 +1,37 @@
1
+ import { render, screen, waitFor } from '@testing-library/react';
2
+ import userEvent from '@testing-library/user-event';
3
+ import { expectNoAxeViolations } from '../../test/a11y';
4
+ import { InviteMembersDialog } from './InviteMembersDialog';
5
+
6
+ describe('InviteMembersDialog', () => {
7
+ it('renders the trigger and an accessible dialog', async () => {
8
+ const { container } = render(<InviteMembersDialog />);
9
+ await expectNoAxeViolations(container);
10
+ await userEvent.click(screen.getByRole('button', { name: 'Invite members' }));
11
+ const dialog = await screen.findByRole('dialog', { name: 'Invite members' });
12
+ await expectNoAxeViolations(dialog);
13
+ });
14
+
15
+ it('validates the emails, then submits and closes', async () => {
16
+ const onInvite = vi.fn().mockResolvedValue(undefined);
17
+ render(<InviteMembersDialog defaultOpen onInvite={onInvite} />);
18
+ const input = await screen.findByLabelText(/Email addresses/);
19
+ await userEvent.click(screen.getByRole('button', { name: 'Send invitation' }));
20
+ expect(await screen.findByText('Add at least one email address.')).toBeInTheDocument();
21
+ expect(onInvite).not.toHaveBeenCalled();
22
+
23
+ await userEvent.type(input, 'not-an-email{Enter}');
24
+ await userEvent.click(screen.getByRole('button', { name: 'Send invitation' }));
25
+ expect(onInvite).not.toHaveBeenCalled();
26
+ await userEvent.click(screen.getByRole('button', { name: 'Remove not-an-email' }));
27
+
28
+ await userEvent.type(input, 'avery@example.com, sam@example.com,');
29
+ await userEvent.click(screen.getByRole('button', { name: 'Send 2 invitations' }));
30
+ expect(onInvite).toHaveBeenCalledWith({
31
+ emails: ['avery@example.com', 'sam@example.com'],
32
+ role: 'member',
33
+ message: '',
34
+ });
35
+ await waitFor(() => expect(screen.queryByRole('dialog')).not.toBeInTheDocument());
36
+ });
37
+ });