@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,245 @@
1
+ import { useId, type ReactNode } from 'react';
2
+ import { Check, Minus } from '@gntik-ai/icons';
3
+ import { Badge, Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow, cn } from '@gntik-ai/ui';
4
+ import { ActionButton, type MarketingAction } from '../actions';
5
+ import { sampleComparisonPlans, sampleComparisonSections } from './fixtures';
6
+
7
+ /** A plan or option: one column of the comparison. */
8
+ export interface ComparisonPlan {
9
+ id: string;
10
+ name: string;
11
+ /** Headline price or figure under the name ("$49", "Custom"). */
12
+ price?: ReactNode;
13
+ /** Small text after the price ("per month"). */
14
+ priceNote?: ReactNode;
15
+ /** Call to action under the price. */
16
+ cta?: MarketingAction;
17
+ }
18
+
19
+ /** A plain value, or a value with a short note under it ("Add-on"). */
20
+ export type ComparisonValue =
21
+ boolean | string | number | null | undefined | { value: boolean | string | number | null; note?: string };
22
+
23
+ export interface ComparisonFeature {
24
+ id: string;
25
+ label: string;
26
+ /** Muted text under the label. */
27
+ description?: string;
28
+ /** Value per plan id. Missing / null values render like `false` (a dash, "Not included"). */
29
+ values: Readonly<Record<string, ComparisonValue>>;
30
+ }
31
+
32
+ export interface ComparisonSection {
33
+ id: string;
34
+ title: string;
35
+ features: ComparisonFeature[];
36
+ }
37
+
38
+ export interface ComparisonTableProps {
39
+ plans?: ComparisonPlan[];
40
+ sections?: ComparisonSection[];
41
+ /** Plan column marked as recommended: badge text, a primary top rule and a tinted column. */
42
+ highlightedPlanId?: string;
43
+ /** Text of the recommended badge (default "Recommended"). */
44
+ highlightLabel?: string;
45
+ eyebrow?: ReactNode;
46
+ title?: ReactNode;
47
+ description?: ReactNode;
48
+ /** Level of the section title (default h2). */
49
+ headingLevel?: 'h2' | 'h3';
50
+ /** Table name (screen-reader caption). Default: the title when it is a string, else "Plan comparison". */
51
+ caption?: string;
52
+ /** Header of the feature column (screen-reader only unless `showFeatureHeader`). */
53
+ featureHeader?: string;
54
+ showFeatureHeader?: boolean;
55
+ /** Screen-reader text of `true` (default "Included"). */
56
+ includedLabel?: string;
57
+ /** Screen-reader text of `false` / missing values (default "Not included"). */
58
+ notIncludedLabel?: string;
59
+ /** Formats numeric values (default: en-US grouping). */
60
+ formatNumber?: (value: number, feature: ComparisonFeature) => string;
61
+ className?: string;
62
+ }
63
+
64
+ const number = new Intl.NumberFormat('en-US');
65
+ const STICKY = 'sticky start-0 z-10 bg-card';
66
+
67
+ /**
68
+ * Feature comparison of plans or options: columns are plans, rows are features grouped by section.
69
+ * Booleans render as a check or a dash with screen-reader text; text and numbers render as is. The
70
+ * table scrolls sideways on narrow screens with the feature column pinned.
71
+ */
72
+ export function ComparisonTable({
73
+ plans = sampleComparisonPlans,
74
+ sections = sampleComparisonSections,
75
+ highlightedPlanId = 'team',
76
+ highlightLabel = 'Recommended',
77
+ eyebrow = 'Compare plans',
78
+ title = 'Find the plan that fits',
79
+ description,
80
+ headingLevel: Heading = 'h2',
81
+ caption,
82
+ featureHeader = 'Feature',
83
+ showFeatureHeader = false,
84
+ includedLabel = 'Included',
85
+ notIncludedLabel = 'Not included',
86
+ formatNumber = (v) => number.format(v),
87
+ className,
88
+ }: ComparisonTableProps) {
89
+ const titleId = useId();
90
+ const hasTitle = title != null && title !== false && title !== '';
91
+ const tableName = caption ?? (typeof title === 'string' && title ? title : 'Plan comparison');
92
+ const columns = plans.length + 1;
93
+ const isHighlighted = (plan: ComparisonPlan) => plan.id === highlightedPlanId;
94
+ const Wrapper = hasTitle ? 'section' : 'div';
95
+
96
+ const renderValue = (raw: ComparisonValue, feature: ComparisonFeature) => {
97
+ const { value, note } = raw !== null && typeof raw === 'object' ? raw : { value: raw, note: undefined };
98
+ let content: ReactNode;
99
+ if (value === true) {
100
+ content = (
101
+ <>
102
+ <Check size={16} aria-hidden className="inline-block text-primary-text" />
103
+ <span className="sr-only">{includedLabel}</span>
104
+ </>
105
+ );
106
+ } else if (value === false || value == null || value === '') {
107
+ content = (
108
+ <>
109
+ <Minus size={16} aria-hidden className="inline-block text-muted-foreground" />
110
+ <span className="sr-only">{notIncludedLabel}</span>
111
+ </>
112
+ );
113
+ } else {
114
+ content = (
115
+ <span className="text-foreground">{typeof value === 'number' ? formatNumber(value, feature) : value}</span>
116
+ );
117
+ }
118
+ return (
119
+ <>
120
+ {content}
121
+ {note && <span className="mt-0.5 block text-[11.5px] text-muted-foreground">{note}</span>}
122
+ </>
123
+ );
124
+ };
125
+
126
+ return (
127
+ <Wrapper aria-labelledby={hasTitle ? titleId : undefined} className={cn('px-4 py-16 sm:px-6 sm:py-20', className)}>
128
+ {(hasTitle || eyebrow || description) && (
129
+ <div className="mx-auto mb-10 max-w-2xl text-center">
130
+ {eyebrow && (
131
+ <p className="font-mono text-[12px] font-semibold tracking-wide text-primary-text uppercase">{eyebrow}</p>
132
+ )}
133
+ {hasTitle && (
134
+ <Heading
135
+ id={titleId}
136
+ className="mt-2 text-[28px] leading-tight font-semibold tracking-tight text-balance text-foreground sm:text-[34px]"
137
+ >
138
+ {title}
139
+ </Heading>
140
+ )}
141
+ {description && (
142
+ <p className="mt-4 text-[15.5px] leading-relaxed text-pretty text-muted-foreground">{description}</p>
143
+ )}
144
+ </div>
145
+ )}
146
+ <div className="mx-auto max-w-6xl overflow-hidden rounded-xl border border-border bg-card shadow-sm">
147
+ <Table containerLabel={`${tableName}, scrollable`} className="min-w-[40rem] table-fixed">
148
+ <TableCaption srOnly>{tableName}</TableCaption>
149
+ <TableHeader>
150
+ <TableRow className="hover:bg-transparent">
151
+ <TableHead className={cn(STICKY, 'w-[34%] min-w-44 align-bottom')}>
152
+ <span className={showFeatureHeader ? undefined : 'sr-only'}>{featureHeader}</span>
153
+ </TableHead>
154
+ {plans.map((plan) => {
155
+ const highlighted = isHighlighted(plan);
156
+ return (
157
+ <TableHead
158
+ key={plan.id}
159
+ data-highlighted={highlighted || undefined}
160
+ className={cn(
161
+ 'h-auto px-4 pt-5 pb-4 align-top tracking-normal whitespace-normal normal-case',
162
+ highlighted ? 'border-t-2 border-t-primary bg-primary/6' : 'bg-card',
163
+ )}
164
+ >
165
+ <span className="flex flex-wrap items-center gap-2">
166
+ <span className="text-[15px] font-semibold text-foreground">{plan.name}</span>
167
+ {highlighted && (
168
+ <Badge tone="primary" shape="pill" size="sm">
169
+ {highlightLabel}
170
+ </Badge>
171
+ )}
172
+ </span>
173
+ {plan.price != null && (
174
+ <span className="mt-2 block">
175
+ <span className="text-[22px] font-semibold tracking-tight text-foreground tabular-nums">
176
+ {plan.price}
177
+ </span>
178
+ {plan.priceNote && (
179
+ <span className="ms-1.5 text-[12px] font-normal text-muted-foreground">{plan.priceNote}</span>
180
+ )}
181
+ </span>
182
+ )}
183
+ {plan.cta && (
184
+ <ActionButton
185
+ action={plan.cta}
186
+ variant={highlighted ? 'primary' : 'secondary'}
187
+ size="md"
188
+ className="mt-4 w-full"
189
+ />
190
+ )}
191
+ </TableHead>
192
+ );
193
+ })}
194
+ </TableRow>
195
+ </TableHeader>
196
+ {sections.map((section) => (
197
+ <TableBody
198
+ key={section.id}
199
+ data-section={section.id}
200
+ className="[&>tr:last-child>td]:border-b [&>tr:last-child>th]:border-b"
201
+ >
202
+ <TableRow className="hover:bg-transparent">
203
+ <TableHead
204
+ scope="rowgroup"
205
+ colSpan={columns}
206
+ className="h-10 bg-secondary/50 text-[12px] font-semibold text-foreground"
207
+ >
208
+ {/* The text stays in view while the table scrolls sideways. */}
209
+ <span className={cn(STICKY, 'bg-transparent')}>{section.title}</span>
210
+ </TableHead>
211
+ </TableRow>
212
+ {section.features.map((feature) => (
213
+ <TableRow key={feature.id}>
214
+ <TableHead
215
+ scope="row"
216
+ className={cn(
217
+ STICKY,
218
+ 'h-auto py-3 text-[13px] font-medium tracking-normal whitespace-normal text-foreground normal-case',
219
+ )}
220
+ >
221
+ {feature.label}
222
+ {feature.description && (
223
+ <span className="mt-0.5 block text-[12px] font-normal text-muted-foreground">
224
+ {feature.description}
225
+ </span>
226
+ )}
227
+ </TableHead>
228
+ {plans.map((plan) => (
229
+ <TableCell
230
+ key={plan.id}
231
+ data-highlighted={isHighlighted(plan) || undefined}
232
+ className={cn('text-[13px]', isHighlighted(plan) && 'bg-primary/6')}
233
+ >
234
+ {renderValue(feature.values[plan.id], feature)}
235
+ </TableCell>
236
+ ))}
237
+ </TableRow>
238
+ ))}
239
+ </TableBody>
240
+ ))}
241
+ </Table>
242
+ </div>
243
+ </Wrapper>
244
+ );
245
+ }
@@ -0,0 +1,10 @@
1
+ import type { BlockMeta } from '../../meta';
2
+
3
+ export const meta: BlockMeta = {
4
+ name: 'Comparison table',
5
+ family: 'marketing',
6
+ status: 'beta',
7
+ description:
8
+ 'Plans or options as columns and feature rows grouped by section (one tbody per section with a rowgroup header). Values are booleans (check or dash with screen-reader text), text or numbers; an optional recommended column is marked with a text badge, a primary top rule and a tint. Scrolls sideways on narrow screens with a sticky feature column.',
9
+ uses: ['Table', 'Badge', 'Button'],
10
+ };
@@ -0,0 +1,48 @@
1
+ import { ComparisonTable, type ComparisonPlan, type ComparisonSection } from '../ComparisonTable';
2
+
3
+ const options: ComparisonPlan[] = [
4
+ { id: 'shared', name: 'Shared runner' },
5
+ { id: 'dedicated', name: 'Dedicated runner' },
6
+ { id: 'self-hosted', name: 'Self-hosted' },
7
+ ];
8
+
9
+ const sections: ComparisonSection[] = [
10
+ {
11
+ id: 'capacity',
12
+ title: 'Capacity',
13
+ features: [
14
+ { id: 'cpu', label: 'vCPU', values: { shared: 2, dedicated: 8, 'self-hosted': 'Your hardware' } },
15
+ { id: 'jobs', label: 'Concurrent jobs', values: { shared: 1, dedicated: 10, 'self-hosted': 'Unlimited' } },
16
+ ],
17
+ },
18
+ {
19
+ id: 'operations',
20
+ title: 'Operations',
21
+ features: [
22
+ { id: 'managed', label: 'Managed updates', values: { shared: true, dedicated: true, 'self-hosted': false } },
23
+ {
24
+ id: 'network',
25
+ label: 'Private networking',
26
+ values: { shared: false, dedicated: { value: true, note: 'VPC peering' }, 'self-hosted': true },
27
+ },
28
+ ],
29
+ },
30
+ ];
31
+
32
+ /** Options (not priced plans) inside a settings page: no title, no CTAs, a recommended column. */
33
+ export default function RunnerOptionsComparison() {
34
+ return (
35
+ <ComparisonTable
36
+ plans={options}
37
+ sections={sections}
38
+ highlightedPlanId="dedicated"
39
+ highlightLabel="Best for teams"
40
+ eyebrow={null}
41
+ title={null}
42
+ caption="Runner options"
43
+ showFeatureHeader
44
+ featureHeader="Capability"
45
+ className="p-0 sm:p-0"
46
+ />
47
+ );
48
+ }
@@ -0,0 +1,83 @@
1
+ import type { ComparisonPlan, ComparisonSection } from './ComparisonTable';
2
+
3
+ /** Three sample plans (generic, product-agnostic). */
4
+ export const sampleComparisonPlans: ComparisonPlan[] = [
5
+ {
6
+ id: 'starter',
7
+ name: 'Starter',
8
+ price: '$0',
9
+ priceNote: 'per month',
10
+ cta: { label: 'Start for free', href: '#starter' },
11
+ },
12
+ {
13
+ id: 'team',
14
+ name: 'Team',
15
+ price: '$49',
16
+ priceNote: 'per member / month',
17
+ cta: { label: 'Start free trial', href: '#team' },
18
+ },
19
+ {
20
+ id: 'enterprise',
21
+ name: 'Enterprise',
22
+ price: 'Custom',
23
+ priceNote: 'billed annually',
24
+ cta: { label: 'Contact sales', href: '#enterprise' },
25
+ },
26
+ ];
27
+
28
+ /** Features grouped by section; values are keyed by plan id. */
29
+ export const sampleComparisonSections: ComparisonSection[] = [
30
+ {
31
+ id: 'usage',
32
+ title: 'Usage',
33
+ features: [
34
+ { id: 'projects', label: 'Projects', values: { starter: 1, team: 'Unlimited', enterprise: 'Unlimited' } },
35
+ { id: 'members', label: 'Members', values: { starter: 3, team: 25, enterprise: 'Unlimited' } },
36
+ {
37
+ id: 'requests',
38
+ label: 'Requests per month',
39
+ values: { starter: 50_000, team: 500_000, enterprise: { value: 'Custom', note: 'Volume pricing' } },
40
+ },
41
+ {
42
+ id: 'retention',
43
+ label: 'Log retention',
44
+ description: 'How long request logs are kept.',
45
+ values: { starter: '7 days', team: '30 days', enterprise: '1 year' },
46
+ },
47
+ ],
48
+ },
49
+ {
50
+ id: 'collaboration',
51
+ title: 'Collaboration',
52
+ features: [
53
+ { id: 'previews', label: 'Preview deployments', values: { starter: false, team: true, enterprise: true } },
54
+ { id: 'roles', label: 'Custom roles', values: { starter: false, team: false, enterprise: true } },
55
+ { id: 'comments', label: 'Comments and reviews', values: { starter: true, team: true, enterprise: true } },
56
+ ],
57
+ },
58
+ {
59
+ id: 'security',
60
+ title: 'Security and compliance',
61
+ features: [
62
+ {
63
+ id: 'sso',
64
+ label: 'Single sign-on',
65
+ values: { starter: false, team: { value: true, note: 'Add-on' }, enterprise: true },
66
+ },
67
+ { id: 'audit', label: 'Audit log', values: { starter: false, team: false, enterprise: true } },
68
+ { id: 'residency', label: 'Data residency', values: { starter: false, team: false, enterprise: true } },
69
+ ],
70
+ },
71
+ {
72
+ id: 'support',
73
+ title: 'Support',
74
+ features: [
75
+ {
76
+ id: 'channel',
77
+ label: 'Support channel',
78
+ values: { starter: 'Community', team: 'Email', enterprise: 'Dedicated' },
79
+ },
80
+ { id: 'sla', label: 'Uptime SLA', values: { starter: null, team: '99.9%', enterprise: '99.99%' } },
81
+ ],
82
+ },
83
+ ];
@@ -0,0 +1,25 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import userEvent from '@testing-library/user-event';
3
+ import { expectNoAxeViolations } from '../../test/a11y';
4
+ import { Faq } from './Faq';
5
+ import { sampleFaqs } from './fixtures';
6
+
7
+ describe('Faq', () => {
8
+ it('renders one trigger per question', async () => {
9
+ const { container } = render(<Faq />);
10
+ expect(screen.getByRole('region', { name: 'Frequently asked questions' })).toBeInTheDocument();
11
+ expect(screen.getAllByRole('button')).toHaveLength(sampleFaqs.length);
12
+ await expectNoAxeViolations(container);
13
+ });
14
+
15
+ it('opens an answer on click', async () => {
16
+ const user = userEvent.setup();
17
+ render(<Faq layout="split" />);
18
+ const trigger = screen.getByRole('button', { name: 'How is usage billed?' });
19
+ expect(trigger).toHaveAttribute('aria-expanded', 'false');
20
+ await user.click(trigger);
21
+ expect(trigger).toHaveAttribute('aria-expanded', 'true');
22
+ expect(screen.getByText(/metered per project/)).toBeVisible();
23
+ await expectNoAxeViolations(document.body);
24
+ });
25
+ });
@@ -0,0 +1,68 @@
1
+ import { useId, type ReactNode } from 'react';
2
+ import { Accordion, AccordionItem, AccordionPanel, AccordionTrigger, cn } from '@gntik-ai/ui';
3
+ import { sampleFaqs } from './fixtures';
4
+
5
+ export interface FaqItem {
6
+ id: string;
7
+ question: string;
8
+ answer: ReactNode;
9
+ }
10
+
11
+ export interface FaqProps {
12
+ items?: FaqItem[];
13
+ title?: ReactNode;
14
+ description?: ReactNode;
15
+ /** `stacked` (heading over the list) or `split` (heading left from `lg`). */
16
+ layout?: 'stacked' | 'split';
17
+ /** Ids of the items open on mount. */
18
+ defaultOpen?: string[];
19
+ /** Allow several items open at once. */
20
+ multiple?: boolean;
21
+ headingLevel?: 'h2' | 'h3';
22
+ className?: string;
23
+ }
24
+
25
+ /** Frequently asked questions in an accordion, with a heading block. */
26
+ export function Faq({
27
+ items = sampleFaqs,
28
+ title = 'Frequently asked questions',
29
+ description = 'Can’t find what you’re looking for? Reach out to our support team.',
30
+ layout = 'stacked',
31
+ defaultOpen,
32
+ multiple = false,
33
+ headingLevel: Heading = 'h2',
34
+ className,
35
+ }: FaqProps) {
36
+ const titleId = useId();
37
+ const split = layout === 'split';
38
+ return (
39
+ <section
40
+ aria-labelledby={titleId}
41
+ className={cn(
42
+ 'px-4 py-16 sm:px-6 sm:py-20',
43
+ split ? 'mx-auto grid max-w-6xl gap-10 lg:grid-cols-[1fr_2fr]' : 'mx-auto max-w-3xl',
44
+ className,
45
+ )}
46
+ >
47
+ <div className={cn(!split && 'text-center')}>
48
+ <Heading
49
+ id={titleId}
50
+ className="text-[28px] leading-tight font-semibold tracking-tight text-balance text-foreground sm:text-[34px]"
51
+ >
52
+ {title}
53
+ </Heading>
54
+ {description && (
55
+ <p className="mt-4 text-[15px] leading-relaxed text-pretty text-muted-foreground">{description}</p>
56
+ )}
57
+ </div>
58
+ <Accordion variant="flush" multiple={multiple} defaultValue={defaultOpen} className={cn(!split && 'mt-10')}>
59
+ {items.map((item) => (
60
+ <AccordionItem key={item.id} value={item.id}>
61
+ <AccordionTrigger headingLevel={Heading === 'h2' ? 3 : 4}>{item.question}</AccordionTrigger>
62
+ <AccordionPanel>{item.answer}</AccordionPanel>
63
+ </AccordionItem>
64
+ ))}
65
+ </Accordion>
66
+ </section>
67
+ );
68
+ }
@@ -0,0 +1,10 @@
1
+ import type { BlockMeta } from '../../meta';
2
+
3
+ export const meta: BlockMeta = {
4
+ name: 'FAQ',
5
+ family: 'marketing',
6
+ status: 'beta',
7
+ description:
8
+ 'Frequently asked questions in an accordion with a heading block, stacked or split layout, single or multiple open items.',
9
+ uses: ['Accordion', 'AccordionItem', 'AccordionTrigger', 'AccordionPanel'],
10
+ };
@@ -0,0 +1,32 @@
1
+ import type { FaqItem } from './Faq';
2
+
3
+ /** Sample product-agnostic questions. */
4
+ export const sampleFaqs: FaqItem[] = [
5
+ {
6
+ id: 'trial',
7
+ question: 'Is there a free trial?',
8
+ answer:
9
+ 'Yes. Every paid plan starts with a 14-day trial, no card required. You can cancel at any time during the trial.',
10
+ },
11
+ {
12
+ id: 'billing',
13
+ question: 'How is usage billed?',
14
+ answer:
15
+ 'Usage is metered per project and billed monthly. You can set a spending limit and get alerts before you reach it.',
16
+ },
17
+ {
18
+ id: 'plans',
19
+ question: 'Can I change plans later?',
20
+ answer: 'Upgrade or downgrade whenever you like. Changes are prorated to the day on your next invoice.',
21
+ },
22
+ {
23
+ id: 'members',
24
+ question: 'Can I invite people outside my organization?',
25
+ answer: 'Yes. Guests only get access to the projects you share with them.',
26
+ },
27
+ {
28
+ id: 'data',
29
+ question: 'Where is my data stored?',
30
+ answer: 'You choose the region when you create a project. Data stays in that region unless you move it.',
31
+ },
32
+ ];
@@ -0,0 +1,19 @@
1
+ import { render, screen, within } from '@testing-library/react';
2
+ import { expectNoAxeViolations } from '../../test/a11y';
3
+ import { FeatureGrid } from './FeatureGrid';
4
+ import { sampleFeatures } from './fixtures';
5
+
6
+ describe('FeatureGrid', () => {
7
+ it('renders a labelled section with one item per feature', async () => {
8
+ const { container } = render(<FeatureGrid />);
9
+ const section = screen.getByRole('region', { name: /All the building blocks/ });
10
+ expect(within(section).getAllByRole('listitem')).toHaveLength(sampleFeatures.length);
11
+ expect(screen.getByRole('heading', { level: 3, name: 'Instant deployments' })).toBeInTheDocument();
12
+ await expectNoAxeViolations(container);
13
+ });
14
+
15
+ it('shifts feature headings with the section level', () => {
16
+ render(<FeatureGrid headingLevel="h3" columns={2} features={sampleFeatures.slice(0, 2)} />);
17
+ expect(screen.getAllByRole('heading', { level: 4 })).toHaveLength(2);
18
+ });
19
+ });
@@ -0,0 +1,79 @@
1
+ import { useId, type ReactNode } from 'react';
2
+ import { cn } from '@gntik-ai/ui';
3
+ import type { LucideIcon } from '@gntik-ai/icons';
4
+ import { sampleFeatures } from './fixtures';
5
+
6
+ export interface Feature {
7
+ icon: LucideIcon;
8
+ title: string;
9
+ description: ReactNode;
10
+ }
11
+
12
+ export interface FeatureGridProps {
13
+ features?: Feature[];
14
+ eyebrow?: ReactNode;
15
+ title?: ReactNode;
16
+ description?: ReactNode;
17
+ /** Columns from `lg` (one column on phones, two on tablets). */
18
+ columns?: 2 | 3 | 4;
19
+ /** Heading level of the section title; feature titles use the next level. */
20
+ headingLevel?: 'h2' | 'h3';
21
+ className?: string;
22
+ }
23
+
24
+ const COLS = { 2: 'lg:grid-cols-2', 3: 'lg:grid-cols-3', 4: 'lg:grid-cols-4' } as const;
25
+
26
+ /** Features section: a heading block over a grid of icon + title + description. */
27
+ export function FeatureGrid({
28
+ features = sampleFeatures,
29
+ eyebrow = 'Everything included',
30
+ title = 'All the building blocks, none of the glue code',
31
+ description = 'The essentials every product needs, designed to work together from day one.',
32
+ columns = 3,
33
+ headingLevel: Heading = 'h2',
34
+ className,
35
+ }: FeatureGridProps) {
36
+ const titleId = useId();
37
+ const ItemHeading = Heading === 'h2' ? 'h3' : 'h4';
38
+ return (
39
+ <section aria-labelledby={title ? titleId : undefined} className={cn('px-4 py-16 sm:px-6 sm:py-20', className)}>
40
+ {(eyebrow || title || description) && (
41
+ <div className="mx-auto max-w-2xl text-center">
42
+ {eyebrow && (
43
+ <p className="font-mono text-[12px] font-semibold tracking-wide text-primary-text uppercase">{eyebrow}</p>
44
+ )}
45
+ {title && (
46
+ <Heading
47
+ id={titleId}
48
+ className="mt-2 text-[28px] leading-tight font-semibold tracking-tight text-balance text-foreground sm:text-[34px]"
49
+ >
50
+ {title}
51
+ </Heading>
52
+ )}
53
+ {description && (
54
+ <p className="mt-4 text-[15.5px] leading-relaxed text-pretty text-muted-foreground">{description}</p>
55
+ )}
56
+ </div>
57
+ )}
58
+ <ul className={cn('mx-auto mt-12 grid max-w-6xl gap-x-8 gap-y-10 sm:grid-cols-2', COLS[columns])}>
59
+ {features.map((f) => {
60
+ const Glyph = f.icon;
61
+ return (
62
+ <li key={f.title} className="flex flex-col">
63
+ <span
64
+ aria-hidden
65
+ className="inline-flex size-10 items-center justify-center rounded-lg bg-primary/14 text-primary-chip-text"
66
+ >
67
+ <Glyph size={19} />
68
+ </span>
69
+ <ItemHeading className="mt-4 text-[15px] font-semibold tracking-tight text-foreground">
70
+ {f.title}
71
+ </ItemHeading>
72
+ <p className="mt-1.5 text-[13.5px] leading-relaxed text-muted-foreground">{f.description}</p>
73
+ </li>
74
+ );
75
+ })}
76
+ </ul>
77
+ </section>
78
+ );
79
+ }
@@ -0,0 +1,10 @@
1
+ import type { BlockMeta } from '../../meta';
2
+
3
+ export const meta: BlockMeta = {
4
+ name: 'Feature grid',
5
+ family: 'marketing',
6
+ status: 'beta',
7
+ description:
8
+ 'Features section: eyebrow, title and description over a responsive grid of icon tile, title and description.',
9
+ uses: ['Icon (lucide)'],
10
+ };
@@ -0,0 +1,36 @@
1
+ import { Gauge, Globe, Layers, Lock, Users, Zap } from '@gntik-ai/icons';
2
+ import type { Feature } from './FeatureGrid';
3
+
4
+ /** Sample product-agnostic features. */
5
+ export const sampleFeatures: Feature[] = [
6
+ {
7
+ icon: Zap,
8
+ title: 'Instant deployments',
9
+ description: 'Push a change and get a preview URL in seconds. Promote it to production with one click.',
10
+ },
11
+ {
12
+ icon: Users,
13
+ title: 'Team access',
14
+ description: 'Invite members, assign roles per project and review every permission change in the audit log.',
15
+ },
16
+ {
17
+ icon: Gauge,
18
+ title: 'Usage you can read',
19
+ description: 'Meters for every quota, alerts before the cap and a forecast for the end of the cycle.',
20
+ },
21
+ {
22
+ icon: Lock,
23
+ title: 'Secure by default',
24
+ description: 'SSO, scoped API keys and encrypted secrets, with no extra configuration to get right.',
25
+ },
26
+ {
27
+ icon: Globe,
28
+ title: 'Multi-region',
29
+ description: 'Run close to your users and keep data in the region you choose.',
30
+ },
31
+ {
32
+ icon: Layers,
33
+ title: 'Composable',
34
+ description: 'Start from a template, extend it with your own components and keep one design system.',
35
+ },
36
+ ];