@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,81 @@
1
+ import { Alert, Button, Link, type AlertTone } from '@gntik-ai/ui';
2
+ import type { LucideIcon } from '@gntik-ai/icons';
3
+ import { useState, type ReactNode } from 'react';
4
+ import type { FeedbackAction } from '../types';
5
+
6
+ export interface InlineCalloutProps {
7
+ tone?: AlertTone;
8
+ title?: ReactNode;
9
+ description?: ReactNode;
10
+ /** Buttons (or links when `href` is set) under the text; the first is the main one. */
11
+ actions?: FeedbackAction[];
12
+ /** Replaces the tone icon; `null` hides it. */
13
+ icon?: LucideIcon | null;
14
+ /** Shows a dismiss button; the callout hides itself unless `open` is controlled. */
15
+ dismissible?: boolean;
16
+ open?: boolean;
17
+ onDismiss?: () => void;
18
+ className?: string;
19
+ }
20
+
21
+ const inlineCalloutDefaultActions: FeedbackAction[] = [
22
+ { label: 'Upgrade plan', variant: 'secondary' },
23
+ { label: 'View usage', href: '#usage' },
24
+ ];
25
+
26
+ /** In-content callout built on Alert: tone, title, body and a row of actions. */
27
+ export function InlineCallout({
28
+ tone = 'warning',
29
+ title = 'You have used 92% of your build minutes',
30
+ description = 'Builds will queue once the monthly limit is reached. Upgrade the plan or reduce concurrent builds.',
31
+ actions = inlineCalloutDefaultActions,
32
+ icon,
33
+ dismissible = false,
34
+ open,
35
+ onDismiss,
36
+ className,
37
+ }: InlineCalloutProps) {
38
+ const [dismissed, setDismissed] = useState(false);
39
+ if (!(open ?? !dismissed)) return null;
40
+ return (
41
+ <Alert
42
+ tone={tone}
43
+ title={title}
44
+ description={description}
45
+ icon={icon}
46
+ className={className}
47
+ onDismiss={
48
+ dismissible
49
+ ? () => {
50
+ setDismissed(true);
51
+ onDismiss?.();
52
+ }
53
+ : undefined
54
+ }
55
+ actions={
56
+ actions.length > 0 ? (
57
+ <>
58
+ {actions.map((a, i) =>
59
+ a.href ? (
60
+ <Link key={a.id ?? a.label} href={a.href} className="text-[12.5px] font-semibold">
61
+ {a.label}
62
+ </Link>
63
+ ) : (
64
+ <Button
65
+ key={a.id ?? a.label}
66
+ size="sm"
67
+ variant={a.variant ?? (i === 0 ? 'secondary' : 'ghost')}
68
+ icon={a.icon}
69
+ disabled={a.disabled}
70
+ onClick={a.onClick}
71
+ >
72
+ {a.label}
73
+ </Button>
74
+ ),
75
+ )}
76
+ </>
77
+ ) : undefined
78
+ }
79
+ />
80
+ );
81
+ }
@@ -0,0 +1,9 @@
1
+ import type { BlockMeta } from '../../meta';
2
+
3
+ export const meta: BlockMeta = {
4
+ name: 'Inline callout',
5
+ family: 'feedback',
6
+ status: 'beta',
7
+ description: 'Alert-based in-content callout with a tone, title, body, action buttons or links and optional dismiss.',
8
+ uses: ['Alert', 'Button', 'Link'],
9
+ };
@@ -0,0 +1,25 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import { expectNoAxeViolations } from '../../test/a11y';
3
+ import { ListSkeleton, PageSkeleton, TableSkeleton } from './Skeletons';
4
+
5
+ describe('Skeletons', () => {
6
+ it('renders the three defaults', async () => {
7
+ const { container } = render(
8
+ <>
9
+ <PageSkeleton />
10
+ <TableSkeleton />
11
+ <ListSkeleton />
12
+ </>,
13
+ );
14
+ expect(screen.getAllByRole('status')).toHaveLength(3);
15
+ expect(screen.getByText('Loading page')).toBeInTheDocument();
16
+ await expectNoAxeViolations(container);
17
+ });
18
+
19
+ it('honours row counts and labels', () => {
20
+ const { container } = render(<ListSkeleton rows={7} label="Loading members" />);
21
+ expect(screen.getByRole('status')).toHaveTextContent('Loading members');
22
+ expect(screen.getByRole('status')).toHaveAttribute('aria-busy', 'true');
23
+ expect(container.querySelectorAll('li')).toHaveLength(7);
24
+ });
25
+ });
@@ -0,0 +1,142 @@
1
+ import { Skeleton, cn, useI18n } from '@gntik-ai/ui';
2
+ import type { ReactNode } from 'react';
3
+
4
+ /** Varying bar widths so placeholder rows don't look stamped. */
5
+ const WIDTHS = ['w-3/5', 'w-2/5', 'w-4/5', 'w-1/2', 'w-2/3'] as const;
6
+ const width = (i: number) => WIDTHS[i % WIDTHS.length];
7
+
8
+ function LoadingRegion({ label, className, children }: { label: string; className?: string; children: ReactNode }) {
9
+ return (
10
+ <div role="status" aria-busy="true" className={className}>
11
+ <span className="sr-only">{label}</span>
12
+ {children}
13
+ </div>
14
+ );
15
+ }
16
+
17
+ export interface TableSkeletonProps {
18
+ rows?: number;
19
+ columns?: number;
20
+ /** Screen-reader announcement. */
21
+ label?: string;
22
+ className?: string;
23
+ }
24
+
25
+ function TableSkeletonBody({ rows, columns }: { rows: number; columns: number }) {
26
+ const middle = Math.max(columns - 2, 0);
27
+ return (
28
+ <>
29
+ <div aria-hidden className="flex items-center gap-4 border-b border-border bg-secondary/40 px-4 py-2.5">
30
+ <Skeleton className="h-2.5 w-1/4" />
31
+ {Array.from({ length: middle }, (_, c) => (
32
+ <Skeleton key={c} className="hidden h-2.5 w-14 sm:block" />
33
+ ))}
34
+ <Skeleton className="ms-auto h-2.5 w-10" />
35
+ </div>
36
+ <div aria-hidden className="divide-y divide-border">
37
+ {Array.from({ length: rows }, (_, r) => (
38
+ <div key={r} className="flex items-center gap-4 px-4 py-3.5">
39
+ <div className="flex w-1/4 min-w-0 items-center gap-2.5">
40
+ <Skeleton className="size-7 shrink-0 rounded-md" />
41
+ <Skeleton className={cn('h-3', width(r))} />
42
+ </div>
43
+ {Array.from({ length: middle }, (_, c) =>
44
+ c === 1 ? (
45
+ <Skeleton key={c} className="hidden h-5 w-16 rounded-full sm:block" />
46
+ ) : (
47
+ <Skeleton key={c} className="hidden h-3 w-14 sm:block" />
48
+ ),
49
+ )}
50
+ <Skeleton className="ms-auto h-3 w-10" />
51
+ </div>
52
+ ))}
53
+ </div>
54
+ </>
55
+ );
56
+ }
57
+
58
+ const tableFrame = 'overflow-hidden rounded-lg border border-border bg-card';
59
+
60
+ /** Table placeholder: header strip plus rows with an avatar cell, text cells and a status pill. */
61
+ export function TableSkeleton({ rows = 5, columns = 5, label: labelProp, className }: TableSkeletonProps) {
62
+ const { t } = useI18n();
63
+ const label = labelProp ?? t('loading.table');
64
+ return (
65
+ <LoadingRegion label={label} className={cn(tableFrame, className)}>
66
+ <TableSkeletonBody rows={rows} columns={columns} />
67
+ </LoadingRegion>
68
+ );
69
+ }
70
+
71
+ export interface ListSkeletonProps {
72
+ rows?: number;
73
+ label?: string;
74
+ className?: string;
75
+ }
76
+
77
+ /** List placeholder: avatar, two text lines, a pill and a row action. */
78
+ export function ListSkeleton({ rows = 4, label: labelProp, className }: ListSkeletonProps) {
79
+ const { t } = useI18n();
80
+ const label = labelProp ?? t('loading.list');
81
+ return (
82
+ <LoadingRegion label={label} className={cn('overflow-hidden rounded-lg border border-border bg-card', className)}>
83
+ <ul aria-hidden className="divide-y divide-border">
84
+ {Array.from({ length: rows }, (_, i) => (
85
+ <li key={i} className="flex items-center gap-3 px-4 py-3.5">
86
+ <Skeleton shape="circle" className="size-9" />
87
+ <div className="min-w-0 flex-1 space-y-2">
88
+ <Skeleton className={cn('h-3', width(i + 2))} />
89
+ <Skeleton className="h-2.5 w-2/5" />
90
+ </div>
91
+ <Skeleton className="hidden h-5 w-14 rounded-full sm:block" />
92
+ <Skeleton className="size-7 shrink-0 rounded-md" />
93
+ </li>
94
+ ))}
95
+ </ul>
96
+ </LoadingRegion>
97
+ );
98
+ }
99
+
100
+ export interface PageSkeletonProps {
101
+ /** Number of stat cards. */
102
+ stats?: number;
103
+ /** Rows of the table under the stats. */
104
+ rows?: number;
105
+ label?: string;
106
+ className?: string;
107
+ }
108
+
109
+ /** Whole-page placeholder: header (title, meta, actions), a stat row and a table. */
110
+ export function PageSkeleton({ stats = 4, rows = 5, label: labelProp, className }: PageSkeletonProps) {
111
+ const { t } = useI18n();
112
+ const label = labelProp ?? t('loading.page');
113
+ return (
114
+ <LoadingRegion label={label} className={cn('space-y-6', className)}>
115
+ <div aria-hidden className="flex flex-wrap items-end justify-between gap-4">
116
+ <div className="min-w-0 flex-1 space-y-3">
117
+ <Skeleton className="h-2.5 w-24" />
118
+ <Skeleton className="h-7 w-56 max-w-full" />
119
+ <Skeleton className="h-3 w-80 max-w-full" />
120
+ </div>
121
+ <div className="flex gap-2.5">
122
+ <Skeleton className="h-9 w-24 rounded-lg" />
123
+ <Skeleton className="h-9 w-32 rounded-lg" />
124
+ </div>
125
+ </div>
126
+ {stats > 0 && (
127
+ <div aria-hidden className="grid grid-cols-2 gap-4 lg:grid-cols-4">
128
+ {Array.from({ length: stats }, (_, i) => (
129
+ <div key={i} className="rounded-lg border border-border bg-card p-4">
130
+ <Skeleton className="h-2.5 w-20" />
131
+ <Skeleton className="mt-3.5 h-7 w-24" />
132
+ <Skeleton className="mt-3 h-2.5 w-14" />
133
+ </div>
134
+ ))}
135
+ </div>
136
+ )}
137
+ <div className={tableFrame}>
138
+ <TableSkeletonBody rows={rows} columns={5} />
139
+ </div>
140
+ </LoadingRegion>
141
+ );
142
+ }
@@ -0,0 +1,10 @@
1
+ import type { BlockMeta } from '../../meta';
2
+
3
+ export const meta: BlockMeta = {
4
+ name: 'Skeletons',
5
+ family: 'feedback',
6
+ status: 'beta',
7
+ description:
8
+ 'Loading placeholders for a whole page, a table and a list; each announces loading once via role="status".',
9
+ uses: ['Skeleton'],
10
+ };
@@ -0,0 +1,29 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import userEvent from '@testing-library/user-event';
3
+ import { expectNoAxeViolations } from '../../test/a11y';
4
+ import { SystemBanner } from './SystemBanner';
5
+
6
+ describe('SystemBanner', () => {
7
+ it('renders the default maintenance banner', async () => {
8
+ const { container } = render(<SystemBanner />);
9
+ expect(screen.getByRole('status')).toHaveTextContent('Scheduled maintenance.');
10
+ expect(screen.getByRole('link', { name: 'View details' })).toBeInTheDocument();
11
+ await expectNoAxeViolations(container);
12
+ });
13
+
14
+ it('announces incidents assertively and hides on dismiss', async () => {
15
+ const onDismiss = vi.fn();
16
+ render(
17
+ <SystemBanner
18
+ kind="incident"
19
+ title="Degraded API."
20
+ message="Elevated error rates in eu-west-1."
21
+ onDismiss={onDismiss}
22
+ />,
23
+ );
24
+ expect(screen.getByRole('alert')).toHaveTextContent('Degraded API.');
25
+ await userEvent.setup().click(screen.getByRole('button', { name: 'Dismiss banner' }));
26
+ expect(onDismiss).toHaveBeenCalledTimes(1);
27
+ expect(screen.queryByRole('alert')).not.toBeInTheDocument();
28
+ });
29
+ });
@@ -0,0 +1,105 @@
1
+ import { CircleAlert, Info, Wrench, X } from '@gntik-ai/icons';
2
+ import type { LucideIcon } from '@gntik-ai/icons';
3
+ import { Button, IconButton, Link, cn } from '@gntik-ai/ui';
4
+ import { useState, type ReactNode } from 'react';
5
+ import type { FeedbackAction } from '../types';
6
+
7
+ export type SystemBannerKind = 'maintenance' | 'incident' | 'info';
8
+
9
+ const KIND: Record<SystemBannerKind, { root: string; accent: string; icon: LucideIcon }> = {
10
+ info: { root: 'border-info/25 bg-info/10', accent: 'text-foreground', icon: Info },
11
+ maintenance: { root: 'border-warning/30 bg-warning/10', accent: 'text-warning-text', icon: Wrench },
12
+ incident: { root: 'border-destructive/30 bg-destructive/10', accent: 'text-destructive-text', icon: CircleAlert },
13
+ };
14
+ const ICON_TONE: Record<SystemBannerKind, string> = {
15
+ info: 'text-info',
16
+ maintenance: 'text-warning-text',
17
+ incident: 'text-destructive-text',
18
+ };
19
+
20
+ export interface SystemBannerProps {
21
+ kind?: SystemBannerKind;
22
+ /** Bold lead-in. */
23
+ title?: ReactNode;
24
+ message?: ReactNode;
25
+ /** Optional call to action (status page, details). */
26
+ action?: FeedbackAction | null;
27
+ /** Shows a dismiss button. Incidents are usually not dismissible. */
28
+ dismissible?: boolean;
29
+ /** Controlled visibility; omit to let the banner hide itself on dismiss. */
30
+ open?: boolean;
31
+ onDismiss?: () => void;
32
+ className?: string;
33
+ }
34
+
35
+ /**
36
+ * Full-width banner above the app chrome for system-wide notices. Incidents are announced
37
+ * assertively (role="alert"); maintenance and info politely (role="status").
38
+ */
39
+ export function SystemBanner({
40
+ kind = 'maintenance',
41
+ title = 'Scheduled maintenance.',
42
+ message = 'Services restart zone by zone on Jul 2 at 02:00 UTC. No downtime expected.',
43
+ action = { label: 'View details', href: '#status' },
44
+ dismissible = true,
45
+ open,
46
+ onDismiss,
47
+ className,
48
+ }: SystemBannerProps) {
49
+ const [dismissed, setDismissed] = useState(false);
50
+ if (!(open ?? !dismissed)) return null;
51
+ const k = KIND[kind];
52
+ const Icon = k.icon;
53
+ return (
54
+ <div
55
+ role={kind === 'incident' ? 'alert' : 'status'}
56
+ data-kind={kind}
57
+ className={cn(
58
+ 'flex w-full flex-col gap-2.5 border-b px-4 py-2.5 sm:flex-row sm:items-center sm:gap-4 sm:px-6',
59
+ k.root,
60
+ className,
61
+ )}
62
+ >
63
+ <div className="flex min-w-0 flex-1 items-start gap-3 sm:items-center">
64
+ <Icon size={17} strokeWidth={1.9} aria-hidden className={cn('mt-0.5 shrink-0 sm:mt-0', ICON_TONE[kind])} />
65
+ <p className="min-w-0 text-[13px] leading-6 text-foreground">
66
+ {title != null && <span className={cn('font-semibold', k.accent)}>{title} </span>}
67
+ {message}
68
+ </p>
69
+ </div>
70
+ {(action || dismissible) && (
71
+ <div className="flex shrink-0 items-center gap-2 ps-7 sm:ps-0">
72
+ {action &&
73
+ (action.href ? (
74
+ <Link href={action.href} className="text-[12.5px] font-semibold">
75
+ {action.label}
76
+ </Link>
77
+ ) : (
78
+ <Button
79
+ size="sm"
80
+ variant={action.variant ?? 'secondary'}
81
+ icon={action.icon}
82
+ disabled={action.disabled}
83
+ onClick={action.onClick}
84
+ >
85
+ {action.label}
86
+ </Button>
87
+ ))}
88
+ {dismissible && (
89
+ <IconButton
90
+ icon={X}
91
+ label="Dismiss banner"
92
+ size="sm"
93
+ variant="ghost"
94
+ className="ms-auto"
95
+ onClick={() => {
96
+ setDismissed(true);
97
+ onDismiss?.();
98
+ }}
99
+ />
100
+ )}
101
+ </div>
102
+ )}
103
+ </div>
104
+ );
105
+ }
@@ -0,0 +1,9 @@
1
+ import type { BlockMeta } from '../../meta';
2
+
3
+ export const meta: BlockMeta = {
4
+ name: 'System banner',
5
+ family: 'feedback',
6
+ status: 'beta',
7
+ description: 'Full-width, dismissible banner for maintenance windows, incidents and system-wide notices.',
8
+ uses: ['Button', 'Link'],
9
+ };
@@ -0,0 +1,14 @@
1
+ export type { FeedbackAction } from './types';
2
+ export * from './SystemBanner/SystemBanner';
3
+ export { meta as systemBannerMeta } from './SystemBanner/block.meta';
4
+ export * from './InlineCallout/InlineCallout';
5
+ export { meta as inlineCalloutMeta } from './InlineCallout/block.meta';
6
+ export * from './EmptyStates/EmptyStates';
7
+ export * from './EmptyStates/fixtures';
8
+ export { meta as emptyStatesMeta } from './EmptyStates/block.meta';
9
+ export * from './Skeletons/Skeletons';
10
+ export { meta as skeletonsMeta } from './Skeletons/block.meta';
11
+ export * from './ConfirmDestructive/ConfirmDestructive';
12
+ export { meta as confirmDestructiveMeta } from './ConfirmDestructive/block.meta';
13
+ export * from './ErrorPanel/ErrorPanel';
14
+ export { meta as errorPanelMeta } from './ErrorPanel/block.meta';
@@ -0,0 +1,13 @@
1
+ import type { ButtonProps } from '@gntik-ai/ui';
2
+ import type { LucideIcon } from '@gntik-ai/icons';
3
+
4
+ /** An action rendered as a kit Button, or as a Link when `href` is set. */
5
+ export interface FeedbackAction {
6
+ id?: string;
7
+ label: string;
8
+ href?: string;
9
+ icon?: LucideIcon;
10
+ variant?: ButtonProps['variant'];
11
+ disabled?: boolean;
12
+ onClick?: () => void;
13
+ }
@@ -0,0 +1,36 @@
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 { CreateApiKeyDialog } from './CreateApiKeyDialog';
5
+
6
+ describe('CreateApiKeyDialog', () => {
7
+ it('renders the trigger and an accessible form', async () => {
8
+ const { container } = render(<CreateApiKeyDialog />);
9
+ await expectNoAxeViolations(container);
10
+ await userEvent.click(screen.getByRole('button', { name: 'Create API key' }));
11
+ await expectNoAxeViolations(await screen.findByRole('dialog', { name: 'Create API key' }));
12
+ });
13
+
14
+ it('creates a key, shows the secret once and discards it on close', async () => {
15
+ const onCreate = vi.fn().mockResolvedValue('sk_test_123');
16
+ render(<CreateApiKeyDialog onCreate={onCreate} />);
17
+ await userEvent.click(screen.getByRole('button', { name: 'Create API key' }));
18
+ await userEvent.click(await screen.findByRole('button', { name: 'Create key' }));
19
+ expect(screen.getByText('Give the key a name.')).toBeInTheDocument();
20
+ expect(onCreate).not.toHaveBeenCalled();
21
+
22
+ await userEvent.type(screen.getByLabelText(/Name/), 'ci');
23
+ await userEvent.click(screen.getByRole('checkbox', { name: 'projects:read' }));
24
+ await userEvent.click(screen.getByRole('button', { name: 'Create key' }));
25
+ expect(onCreate).toHaveBeenCalledWith({ name: 'ci', scopes: ['projects:read'], expiry: '90d' });
26
+ const dialog = await screen.findByRole('dialog', { name: 'API key created' });
27
+ expect(screen.getByRole('region', { name: 'New API key' })).toHaveTextContent('sk_test_123');
28
+ await expectNoAxeViolations(dialog);
29
+
30
+ await userEvent.click(screen.getByRole('button', { name: 'Done' }));
31
+ await waitFor(() => expect(screen.queryByRole('dialog')).not.toBeInTheDocument());
32
+ await userEvent.click(screen.getByRole('button', { name: 'Create API key' }));
33
+ expect(await screen.findByRole('dialog', { name: 'Create API key' })).toBeInTheDocument();
34
+ expect(screen.queryByText('sk_test_123')).not.toBeInTheDocument();
35
+ });
36
+ });
@@ -0,0 +1,197 @@
1
+ import {
2
+ Alert,
3
+ Button,
4
+ Checkbox,
5
+ CheckboxGroup,
6
+ CodeBlock,
7
+ Dialog,
8
+ DialogBody,
9
+ DialogClose,
10
+ DialogContent,
11
+ DialogDescription,
12
+ DialogFooter,
13
+ DialogHeader,
14
+ DialogTitle,
15
+ DialogTrigger,
16
+ Field,
17
+ FieldError,
18
+ FieldLabel,
19
+ Fieldset,
20
+ FieldsetLegend,
21
+ Input,
22
+ Select,
23
+ SelectContent,
24
+ SelectItem,
25
+ SelectTrigger,
26
+ } from '@gntik-ai/ui';
27
+ import { KeyRound } from '@gntik-ai/icons';
28
+ import { useState, type ReactElement } from 'react';
29
+ import { apiKeyExpiries, apiKeyScopes, generateDemoSecret, type ApiKeyExpiry, type ApiKeyScope } from './fixtures';
30
+
31
+ export type { ApiKeyExpiry, ApiKeyScope } from './fixtures';
32
+
33
+ export interface CreateApiKeyInput {
34
+ name: string;
35
+ scopes: string[];
36
+ expiry: string;
37
+ }
38
+
39
+ export interface CreateApiKeyDialogProps {
40
+ scopes?: ApiKeyScope[];
41
+ expiries?: ApiKeyExpiry[];
42
+ defaultExpiry?: string;
43
+ /** Creates the key and returns its secret (shown once). Defaults to a demo generator. */
44
+ onCreate?: (input: CreateApiKeyInput) => string | Promise<string>;
45
+ open?: boolean;
46
+ defaultOpen?: boolean;
47
+ onOpenChange?: (open: boolean) => void;
48
+ /** Trigger element (rendered through Dialog.Trigger). Pass `null` when controlling `open` yourself. */
49
+ trigger?: ReactElement | null;
50
+ }
51
+
52
+ /**
53
+ * Two-step API key dialog: name, scopes and expiry, then the secret shown once in a CodeBlock
54
+ * with copy. Closing the dialog discards the secret; it can't be shown again.
55
+ */
56
+ export function CreateApiKeyDialog({
57
+ scopes = apiKeyScopes,
58
+ expiries = apiKeyExpiries,
59
+ defaultExpiry = '90d',
60
+ onCreate = () => generateDemoSecret(),
61
+ open: openProp,
62
+ defaultOpen = false,
63
+ onOpenChange,
64
+ trigger = <Button icon={KeyRound} />,
65
+ }: CreateApiKeyDialogProps) {
66
+ const [innerOpen, setInnerOpen] = useState(defaultOpen);
67
+ const [name, setName] = useState('');
68
+ const [selected, setSelected] = useState<string[]>([]);
69
+ const [expiry, setExpiry] = useState(defaultExpiry);
70
+ const [submitted, setSubmitted] = useState(false);
71
+ const [loading, setLoading] = useState(false);
72
+ const [error, setError] = useState<string | null>(null);
73
+ const [secret, setSecret] = useState<string | null>(null);
74
+ const open = openProp ?? innerOpen;
75
+ const nameInvalid = submitted && name.trim() === '';
76
+ const scopesInvalid = submitted && selected.length === 0;
77
+
78
+ const setOpen = (next: boolean) => {
79
+ if (loading && !next) return;
80
+ if (!next) {
81
+ setName('');
82
+ setSelected([]);
83
+ setExpiry(defaultExpiry);
84
+ setSubmitted(false);
85
+ setError(null);
86
+ setSecret(null);
87
+ }
88
+ setInnerOpen(next);
89
+ onOpenChange?.(next);
90
+ };
91
+
92
+ const submit = async () => {
93
+ setSubmitted(true);
94
+ setError(null);
95
+ if (name.trim() === '' || selected.length === 0) return;
96
+ setLoading(true);
97
+ try {
98
+ const value = await onCreate({ name: name.trim(), scopes: selected, expiry });
99
+ setSecret(value);
100
+ } catch (e) {
101
+ setError(e instanceof Error ? e.message : 'The key could not be created.');
102
+ }
103
+ setLoading(false);
104
+ };
105
+
106
+ return (
107
+ <Dialog open={open} onOpenChange={setOpen}>
108
+ {trigger && <DialogTrigger render={trigger}>Create API key</DialogTrigger>}
109
+ <DialogContent size="lg">
110
+ {secret == null ? (
111
+ <>
112
+ <DialogHeader>
113
+ <DialogTitle>Create API key</DialogTitle>
114
+ <DialogDescription>
115
+ Keys act on behalf of this workspace. Grant only the scopes the integration needs.
116
+ </DialogDescription>
117
+ </DialogHeader>
118
+ <form
119
+ noValidate
120
+ onSubmit={(e) => {
121
+ e.preventDefault();
122
+ void submit();
123
+ }}
124
+ >
125
+ <DialogBody className="grid gap-5">
126
+ <Field invalid={nameInvalid}>
127
+ <FieldLabel required>Name</FieldLabel>
128
+ <Input
129
+ value={name}
130
+ onValueChange={setName}
131
+ placeholder="ci-deployments"
132
+ required
133
+ autoComplete="off"
134
+ />
135
+ <FieldError match={nameInvalid}>Give the key a name.</FieldError>
136
+ </Field>
137
+ <Fieldset
138
+ render={<CheckboxGroup value={selected} onValueChange={setSelected} />}
139
+ aria-invalid={scopesInvalid || undefined}
140
+ >
141
+ <FieldsetLegend>Scopes</FieldsetLegend>
142
+ {scopes.map((s) => (
143
+ <Checkbox
144
+ key={s.value}
145
+ value={s.value}
146
+ label={<span className="font-mono">{s.label}</span>}
147
+ description={s.description}
148
+ />
149
+ ))}
150
+ {scopesInvalid && <p className="text-[12.5px] text-destructive-text">Select at least one scope.</p>}
151
+ </Fieldset>
152
+ <Field>
153
+ <FieldLabel>Expiration</FieldLabel>
154
+ <Select value={expiry} onValueChange={(v) => v != null && setExpiry(v)} items={expiries}>
155
+ <SelectTrigger className="w-full sm:w-56" />
156
+ <SelectContent>
157
+ {expiries.map((x) => (
158
+ <SelectItem key={x.value} value={x.value}>
159
+ {x.label}
160
+ </SelectItem>
161
+ ))}
162
+ </SelectContent>
163
+ </Select>
164
+ </Field>
165
+ {error && <Alert tone="destructive" title="Key not created" description={error} />}
166
+ </DialogBody>
167
+ <DialogFooter>
168
+ <DialogClose render={<Button variant="ghost" disabled={loading} />}>Cancel</DialogClose>
169
+ <Button type="submit" loading={loading}>
170
+ Create key
171
+ </Button>
172
+ </DialogFooter>
173
+ </form>
174
+ </>
175
+ ) : (
176
+ <>
177
+ <DialogHeader>
178
+ <DialogTitle>API key created</DialogTitle>
179
+ <DialogDescription>Store it in your secrets manager. Use it as a bearer token.</DialogDescription>
180
+ </DialogHeader>
181
+ <DialogBody className="grid gap-4">
182
+ <Alert
183
+ tone="warning"
184
+ title="Copy this key now"
185
+ description="For your security it is shown only once. If you lose it, revoke it and create a new one."
186
+ />
187
+ <CodeBlock code={secret} label="New API key" wrapToggle={false} defaultWrap />
188
+ </DialogBody>
189
+ <DialogFooter>
190
+ <DialogClose render={<Button />}>Done</DialogClose>
191
+ </DialogFooter>
192
+ </>
193
+ )}
194
+ </DialogContent>
195
+ </Dialog>
196
+ );
197
+ }