@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,26 @@
1
+ import { render } from '@testing-library/react';
2
+ import { PlanCard, type BillingPlan } from './PlanCard';
3
+
4
+ const full: BillingPlan = {
5
+ name: 'Fixture Plan',
6
+ status: 'active',
7
+ priceMonthly: 10,
8
+ priceAnnual: 100,
9
+ currency: 'EUR',
10
+ renewsOn: '2027-01-01',
11
+ summary: '2 seats',
12
+ features: ['Feature A', 'Feature B'],
13
+ };
14
+
15
+ // Keep text-node spaces inline while retaining the exact rendered HTML baseline.
16
+ describe('PlanCard billing DOM baseline', () => {
17
+ it('preserves the no-props billing header', () => {
18
+ const { container } = render(<PlanCard />);
19
+ expect(container.outerHTML).toMatchSnapshot();
20
+ });
21
+
22
+ it('preserves the FULL billing header', () => {
23
+ const { container } = render(<PlanCard plan={full} upgradeLabel="Upgrade" manageLabel="Manage" />);
24
+ expect(container.outerHTML).toMatchSnapshot();
25
+ });
26
+ });
@@ -0,0 +1,227 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import userEvent from '@testing-library/user-event';
3
+ import { Button } from '@gntik-ai/ui';
4
+ import { expectNoAxeViolations } from '../../test/a11y';
5
+ import { PlanCard, type PlanSummaryProps } from './PlanCard';
6
+
7
+ const minimal = { name: 'Fixture Plan', statusLabel: 'Activo' };
8
+ const full = {
9
+ ...minimal,
10
+ priceMonthly: 10,
11
+ priceAnnual: 100,
12
+ currency: 'EUR',
13
+ cycle: 'monthly' as const,
14
+ renewsOn: '2027-01-01',
15
+ features: ['Feature A', 'Feature B'],
16
+ };
17
+ const fixtures: Array<[string, Omit<PlanSummaryProps, 'variant'>]> = [
18
+ [
19
+ 'FULL',
20
+ {
21
+ plan: full,
22
+ actions: (
23
+ <>
24
+ <Button>Upgrade</Button>
25
+ <Button>Manage</Button>
26
+ </>
27
+ ),
28
+ },
29
+ ],
30
+ ['MINIMAL', { plan: minimal }],
31
+ ['UNASSIGNED', { emptyLabel: 'Sin plan asignado' }],
32
+ ['UNASSIGNED-NO-LABEL', {}],
33
+ ['PRICE-ONLY', { plan: { ...minimal, priceMonthly: 10, currency: 'EUR' } }],
34
+ ['ZERO-PRICE', { plan: { ...minimal, priceMonthly: 0, currency: 'EUR' } }],
35
+ ['RENEWAL-ONLY', { plan: { ...minimal, renewsOn: '2027-01-01' } }],
36
+ ['FEATURES-ONLY', { plan: { ...minimal, features: ['Feature A'] } }],
37
+ ['ACTIONS-ONLY', { plan: minimal, actions: <Button>Manage</Button> }],
38
+ ];
39
+
40
+ describe('PlanCard summary', () => {
41
+ it('renders all FULL values and focusable consumer actions', async () => {
42
+ const user = userEvent.setup();
43
+ render(<PlanCard variant="summary" {...fixtures[0]![1]} />);
44
+ expect(screen.getByRole('heading', { name: 'Fixture Plan' })).toBeInTheDocument();
45
+ expect(screen.getByText('Activo')).toBeInTheDocument();
46
+ expect(screen.getByText('€10')).toBeInTheDocument();
47
+ expect(screen.getByText('Billed monthly')).toBeInTheDocument();
48
+ expect(screen.getByText('Renews January 1, 2027')).toBeInTheDocument();
49
+ expect(screen.getAllByRole('listitem').map((item) => item.textContent)).toEqual(['Feature A', 'Feature B']);
50
+ await user.click(screen.getByRole('button', { name: 'Annual' }));
51
+ expect(screen.getByText('€100')).toBeInTheDocument();
52
+ expect(screen.getByText('Billed annually')).toBeInTheDocument();
53
+ for (const name of ['Upgrade', 'Manage']) {
54
+ const button = screen.getByRole('button', { name });
55
+ expect(button.tagName).toBe('BUTTON');
56
+ button.focus();
57
+ expect(button).toHaveFocus();
58
+ }
59
+ });
60
+
61
+ it.each(['dark', 'light', 'high_contrast'])('has no axe violations for every fixture in %s', async (theme) => {
62
+ document.documentElement.className = theme;
63
+ for (const [, props] of fixtures) {
64
+ const { container, unmount } = render(<PlanCard variant="summary" {...props} />);
65
+ await expectNoAxeViolations(container);
66
+ unmount();
67
+ }
68
+ });
69
+ it('renders ACTIONS-ONLY as a focusable native button with Enter and Space activation', async () => {
70
+ const user = userEvent.setup();
71
+ const onManage = vi.fn();
72
+ render(<PlanCard variant="summary" plan={minimal} actions={<Button onClick={onManage}>Manage</Button>} />);
73
+ const manage = screen.getByRole('button', { name: 'Manage' });
74
+ expect(manage.tagName).toBe('BUTTON');
75
+ expect(screen.queryByText(/€|\$|Billed|Renews/)).toBeNull();
76
+ expect(screen.queryByRole('list')).toBeNull();
77
+ await user.tab();
78
+ expect(manage).toHaveFocus();
79
+ await user.keyboard('{Enter}');
80
+ expect(onManage).toHaveBeenCalledTimes(1);
81
+ await user.keyboard(' ');
82
+ expect(onManage).toHaveBeenCalledTimes(2);
83
+ });
84
+ it('renders FEATURES-ONLY as an accessible list without billing or actions', () => {
85
+ render(<PlanCard variant="summary" plan={{ ...minimal, features: ['Feature A'] }} />);
86
+ expect(screen.getByRole('list', { name: 'Plan features' })).toBeInTheDocument();
87
+ expect(screen.getByRole('listitem')).toHaveTextContent('Feature A');
88
+ expect(screen.queryByText(/€|\$|Billed|Renews/)).toBeNull();
89
+ expect(screen.queryByRole('button')).toBeNull();
90
+ });
91
+ it('renders RENEWAL-ONLY without any other optional region', () => {
92
+ render(<PlanCard variant="summary" plan={{ ...minimal, renewsOn: '2027-01-01' }} />);
93
+ expect(screen.getByText('Renews January 1, 2027')).toBeInTheDocument();
94
+ expect(screen.queryByText(/€|\$|Billed/)).toBeNull();
95
+ expect(screen.queryByRole('button')).toBeNull();
96
+ expect(screen.queryByRole('list')).toBeNull();
97
+ });
98
+ it.each([
99
+ ['PRICE-ONLY', 10, '€10'],
100
+ ['ZERO-PRICE', 0, '€0'],
101
+ ] as const)('renders %s without cycle text or a focusable toggle', async (_fixture, priceMonthly, amount) => {
102
+ const user = userEvent.setup();
103
+ const { container } = render(<PlanCard variant="summary" plan={{ ...minimal, priceMonthly, currency: 'EUR' }} />);
104
+ expect(screen.getByText(amount)).toBeInTheDocument();
105
+ expect(screen.queryByRole('group')).toBeNull();
106
+ expect(screen.queryByRole('button')).toBeNull();
107
+ expect(screen.queryByText(/Billed|Renews|month|annual/i)).toBeNull();
108
+ expect(screen.queryByRole('list')).toBeNull();
109
+ await user.tab();
110
+ expect(document.body).toHaveFocus();
111
+ await expectNoAxeViolations(container);
112
+ });
113
+
114
+ it('shows supplied monthly and annual totals verbatim with an accessible toggle', async () => {
115
+ const user = userEvent.setup();
116
+ const onCycleChange = vi.fn();
117
+ render(
118
+ <PlanCard
119
+ variant="summary"
120
+ plan={{
121
+ ...minimal,
122
+ priceMonthly: 10,
123
+ priceAnnual: 100,
124
+ currency: 'EUR',
125
+ }}
126
+ onCycleChange={onCycleChange}
127
+ />,
128
+ );
129
+ expect(screen.getByText('€10')).toBeInTheDocument();
130
+ const monthly = screen.getByRole('button', {
131
+ name: 'Monthly',
132
+ });
133
+ const annual = screen.getByRole('button', { name: 'Annual' });
134
+ expect(monthly).toHaveAttribute('aria-pressed', 'true');
135
+ await user.tab();
136
+ expect(monthly).toHaveFocus();
137
+ await user.click(annual);
138
+ expect(screen.getByText('€100')).toBeInTheDocument();
139
+ expect(screen.queryByText('€1,200')).toBeNull();
140
+ expect(annual).toHaveAttribute('aria-pressed', 'true');
141
+ expect(onCycleChange).toHaveBeenCalledWith('annual');
142
+ await user.click(monthly);
143
+ expect(screen.getByText('€10')).toBeInTheDocument();
144
+ });
145
+
146
+ it('renders an annual-only price without a toggle', () => {
147
+ render(<PlanCard variant="summary" plan={{ ...minimal, priceAnnual: 100, currency: 'EUR' }} />);
148
+ expect(screen.getByText('€100')).toBeInTheDocument();
149
+ expect(screen.queryByRole('button')).toBeNull();
150
+ expect(screen.queryByText(/Billed/)).toBeNull();
151
+ });
152
+
153
+ it('renders supplied cycle text independently of price', () => {
154
+ render(<PlanCard variant="summary" plan={{ ...minimal, cycle: 'annual' }} />);
155
+ expect(screen.getByText('Billed annually')).toBeInTheDocument();
156
+ expect(screen.queryByText(/€|\$/)).toBeNull();
157
+ expect(screen.queryByRole('button')).toBeNull();
158
+ });
159
+
160
+ it('renders a name without inventing a status or any optional content', () => {
161
+ const { container } = render(<PlanCard variant="summary" plan={{ name: 'Fixture Plan' }} />);
162
+ expect(screen.getByRole('heading', { name: 'Fixture Plan' })).toBeInTheDocument();
163
+ expect(container.textContent).toBe('Fixture Plan');
164
+ expect(screen.queryByText('Active')).toBeNull();
165
+ expect(screen.queryByRole('button')).toBeNull();
166
+ expect(screen.queryByRole('group')).toBeNull();
167
+ expect(screen.queryByRole('list')).toBeNull();
168
+ });
169
+
170
+ it('keeps zero annual pricing selectable with the keyboard', async () => {
171
+ const user = userEvent.setup();
172
+ render(<PlanCard variant="summary" plan={{ ...minimal, priceMonthly: 10, priceAnnual: 0, currency: 'EUR' }} />);
173
+ const monthly = screen.getByRole('button', { name: 'Monthly' });
174
+ const annual = screen.getByRole('button', { name: 'Annual' });
175
+ expect(screen.getByText('€10')).toBeInTheDocument();
176
+ await user.tab();
177
+ expect(monthly).toHaveFocus();
178
+ await user.keyboard('{ArrowRight}');
179
+ expect(annual).toHaveFocus();
180
+ await user.keyboard(' ');
181
+ expect(screen.getByText('€0')).toBeInTheDocument();
182
+ expect(annual).toHaveAttribute('aria-pressed', 'true');
183
+ expect(monthly).toHaveAttribute('aria-pressed', 'false');
184
+ await user.keyboard('{ArrowLeft}{Enter}');
185
+ expect(screen.getByText('€10')).toBeInTheDocument();
186
+ expect(monthly).toHaveAttribute('aria-pressed', 'true');
187
+ expect(annual).toHaveAttribute('aria-pressed', 'false');
188
+ });
189
+
190
+ it('renders only the consumer name and status for MINIMAL', () => {
191
+ const { container } = render(<PlanCard variant="summary" plan={minimal} />);
192
+ expect(screen.getByRole('heading', { name: 'Fixture Plan' })).toBeInTheDocument();
193
+ expect(screen.getByText('Activo')).toBeInTheDocument();
194
+ expect(screen.queryByRole('button')).toBeNull();
195
+ expect(screen.queryByRole('group')).toBeNull();
196
+ expect(screen.queryByRole('list')).toBeNull();
197
+ expect(container.textContent).toBe('Fixture PlanActivo');
198
+ expect(screen.queryByText(/Scale|\$|Billed|Renews|Active/)).toBeNull();
199
+ });
200
+
201
+ it('renders UNASSIGNED using only the consumer empty label', () => {
202
+ const { container } = render(<PlanCard variant="summary" emptyLabel="Sin plan asignado" />);
203
+ expect(screen.getByRole('heading', { name: 'Sin plan asignado' })).toBeInTheDocument();
204
+ expect(screen.queryByRole('status')).toBeNull();
205
+ expect(screen.queryByRole('button')).toBeNull();
206
+ expect(container.textContent).toBe('Sin plan asignado');
207
+ });
208
+
209
+ it('renders no heading for UNASSIGNED-NO-LABEL and warns only in development', () => {
210
+ const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
211
+ try {
212
+ vi.stubEnv('NODE_ENV', 'development');
213
+ const { container, unmount } = render(<PlanCard variant="summary" />);
214
+ expect(screen.queryByRole('heading')).toBeNull();
215
+ expect(container.textContent).toBe('');
216
+ expect(warn).toHaveBeenCalledWith('PlanCard summary: supply a plan name or emptyLabel.');
217
+ unmount();
218
+ warn.mockClear();
219
+ vi.stubEnv('NODE_ENV', 'production');
220
+ render(<PlanCard variant="summary" />);
221
+ expect(warn).not.toHaveBeenCalled();
222
+ } finally {
223
+ vi.unstubAllEnvs();
224
+ warn.mockRestore();
225
+ }
226
+ });
227
+ });
@@ -0,0 +1,27 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import userEvent from '@testing-library/user-event';
3
+ import { expectNoAxeViolations } from '../../test/a11y';
4
+ import { PlanCard } from './PlanCard';
5
+
6
+ describe('PlanCard', () => {
7
+ it('renders the sample plan', async () => {
8
+ const { container } = render(<PlanCard />);
9
+ expect(screen.getByRole('heading', { name: 'Scale plan' })).toBeInTheDocument();
10
+ expect(screen.getByText('$1,200')).toBeInTheDocument();
11
+ expect(screen.getByText(/Renews June 1, 2026/)).toBeInTheDocument();
12
+ await expectNoAxeViolations(container);
13
+ });
14
+
15
+ it('switches the cycle and fires the actions', async () => {
16
+ const user = userEvent.setup();
17
+ const onUpgrade = vi.fn();
18
+ const onCycleChange = vi.fn();
19
+ render(<PlanCard onUpgrade={onUpgrade} onCycleChange={onCycleChange} />);
20
+ await user.click(screen.getByRole('button', { name: /Annual/ }));
21
+ expect(onCycleChange).toHaveBeenCalledWith('annual');
22
+ expect(screen.getByText('$1,000')).toBeInTheDocument();
23
+ expect(screen.getByText(/Billed annually/)).toBeInTheDocument();
24
+ await user.click(screen.getByRole('button', { name: 'Upgrade plan' }));
25
+ expect(onUpgrade).toHaveBeenCalledOnce();
26
+ });
27
+ });
@@ -0,0 +1,140 @@
1
+ import { useState } from 'react';
2
+ import { Badge, Button, Card, Toggle, ToggleGroup, cn, type BadgeTone } from '@gntik-ai/ui';
3
+ import { Check, Sparkles } from '@gntik-ai/icons';
4
+ import { formatDate, formatMoney, type DateInput } from '../format';
5
+ import { samplePlan } from './fixtures';
6
+ import { PlanSummary, type PlanSummaryProps } from './PlanSummary';
7
+ export type { SummaryPlan, PlanSummaryProps } from './PlanSummary';
8
+
9
+ export type PlanStatus = 'active' | 'trialing' | 'past_due' | 'canceled';
10
+ export type BillingCycle = 'monthly' | 'annual';
11
+
12
+ export interface BillingPlan {
13
+ name: string;
14
+ status?: PlanStatus;
15
+ /** Price per month on the monthly cycle. */
16
+ priceMonthly: number;
17
+ /** Price per month when billed annually. Enables the monthly/annual toggle. */
18
+ priceAnnual?: number;
19
+ /** ISO 4217 code. Default USD. */
20
+ currency?: string;
21
+ /** Next renewal date. */
22
+ renewsOn: DateInput;
23
+ /** Short line after the renewal date, e.g. "50 seats · 2 regions". */
24
+ summary?: string;
25
+ features?: string[];
26
+ }
27
+
28
+ export interface BillingPlanCardProps {
29
+ variant?: 'billing';
30
+ plan?: BillingPlan;
31
+ /** Initial cycle shown (uncontrolled). */
32
+ defaultCycle?: BillingCycle;
33
+ onCycleChange?: (cycle: BillingCycle) => void;
34
+ onUpgrade?: () => void;
35
+ onManage?: () => void;
36
+ upgradeLabel?: string;
37
+ manageLabel?: string;
38
+ /** Heading level of the title, to fit the page outline (default h3). */
39
+ titleAs?: 'h2' | 'h3' | 'h4';
40
+ className?: string;
41
+ }
42
+
43
+ export type PlanCardProps = BillingPlanCardProps | PlanSummaryProps;
44
+
45
+ /** Default billing header, or an opt-in summary without sample data. */
46
+ export function PlanCard(props: PlanCardProps) {
47
+ return props.variant === 'summary' ? <PlanSummary {...props} /> : <BillingPlanCard {...props} />;
48
+ }
49
+
50
+ const STATUS: Record<PlanStatus, { label: string; tone: BadgeTone }> = {
51
+ active: { label: 'Active', tone: 'success' },
52
+ trialing: { label: 'Trial', tone: 'info' },
53
+ past_due: { label: 'Past due', tone: 'warning' },
54
+ canceled: { label: 'Canceled', tone: 'neutral' },
55
+ };
56
+
57
+ /** Billing page header: the current plan, its price per cycle, renewal date, features and actions. */
58
+ function BillingPlanCard({
59
+ plan = samplePlan,
60
+ defaultCycle = 'monthly',
61
+ onCycleChange,
62
+ onUpgrade,
63
+ onManage,
64
+ upgradeLabel = 'Upgrade plan',
65
+ manageLabel = 'Manage',
66
+ titleAs: TitleTag = 'h3',
67
+ className,
68
+ }: BillingPlanCardProps) {
69
+ const [cycle, setCycle] = useState<BillingCycle>(defaultCycle);
70
+ const hasAnnual = plan.priceAnnual != null;
71
+ const annual = hasAnnual && cycle === 'annual';
72
+ const perMonth = annual ? (plan.priceAnnual ?? plan.priceMonthly) : plan.priceMonthly;
73
+ const currency = plan.currency ?? 'USD';
74
+ const savings = hasAnnual ? Math.round((1 - (plan.priceAnnual ?? 0) / plan.priceMonthly) * 100) : 0;
75
+ const status = STATUS[plan.status ?? 'active'];
76
+
77
+ const changeCycle = (value: string[]) => {
78
+ const next = value[0];
79
+ if (next !== 'monthly' && next !== 'annual') return;
80
+ setCycle(next);
81
+ onCycleChange?.(next);
82
+ };
83
+
84
+ return (
85
+ <Card className={cn('p-6', className)}>
86
+ <div className="flex flex-col gap-5 sm:flex-row sm:items-start sm:justify-between">
87
+ <div className="min-w-0">
88
+ <div className="flex flex-wrap items-center gap-2">
89
+ <TitleTag className="text-[17px] font-semibold tracking-tight text-foreground">{plan.name} plan</TitleTag>
90
+ <Badge tone={status.tone} dot>
91
+ {status.label}
92
+ </Badge>
93
+ </div>
94
+ <p className="mt-1 text-[13px] text-muted-foreground">
95
+ Renews {formatDate(plan.renewsOn)}
96
+ {plan.summary && <> · {plan.summary}</>}
97
+ </p>
98
+ <p className="mt-4 flex items-baseline gap-1.5">
99
+ <span className="text-[30px] font-semibold tracking-tight text-foreground tabular-nums">
100
+ {formatMoney(perMonth, currency, true)}
101
+ </span>
102
+ <span className="text-[13px] text-muted-foreground">/ month</span>
103
+ </p>
104
+ <p className="mt-1 text-[12px] text-muted-foreground">
105
+ {annual ? `Billed annually (${formatMoney(perMonth * 12, currency, true)})` : 'Billed monthly'}
106
+ </p>
107
+ </div>
108
+ <div className="flex flex-col items-start gap-3 sm:items-end">
109
+ {hasAnnual && (
110
+ <ToggleGroup size="sm" aria-label="Billing cycle" value={[cycle]} onValueChange={changeCycle}>
111
+ <Toggle value="monthly">Monthly</Toggle>
112
+ <Toggle value="annual">Annual{savings > 0 && ` −${savings}%`}</Toggle>
113
+ </ToggleGroup>
114
+ )}
115
+ <div className="flex flex-wrap items-center gap-2">
116
+ <Button variant="secondary" onClick={onManage}>
117
+ {manageLabel}
118
+ </Button>
119
+ <Button icon={Sparkles} onClick={onUpgrade}>
120
+ {upgradeLabel}
121
+ </Button>
122
+ </div>
123
+ </div>
124
+ </div>
125
+ {plan.features && plan.features.length > 0 && (
126
+ <ul
127
+ aria-label={`${plan.name} plan features`}
128
+ className="mt-5 grid gap-x-6 gap-y-2 border-t border-border pt-5 sm:grid-cols-2"
129
+ >
130
+ {plan.features.map((f) => (
131
+ <li key={f} className="flex items-start gap-2 text-[13px] text-foreground">
132
+ <Check size={15} aria-hidden className="mt-0.5 shrink-0 text-primary-text" />
133
+ {f}
134
+ </li>
135
+ ))}
136
+ </ul>
137
+ )}
138
+ </Card>
139
+ );
140
+ }
@@ -0,0 +1,101 @@
1
+ import { Badge, Card, Toggle, ToggleGroup, cn, type BadgeTone } from '@gntik-ai/ui';
2
+ import { useEffect, useState, type ReactNode } from 'react';
3
+ import { formatDate, formatMoney, type DateInput } from '../format';
4
+ import type { BillingCycle } from './PlanCard';
5
+
6
+ /** Consumer data only: each summary region is optional. Prices are cycle totals. */
7
+ export interface SummaryPlan {
8
+ name?: string;
9
+ statusLabel?: string;
10
+ statusTone?: BadgeTone;
11
+ priceMonthly?: number;
12
+ priceAnnual?: number;
13
+ currency?: string;
14
+ cycle?: BillingCycle;
15
+ renewsOn?: DateInput;
16
+ features?: string[];
17
+ }
18
+
19
+ export interface PlanSummaryProps {
20
+ variant: 'summary';
21
+ plan?: SummaryPlan;
22
+ emptyLabel?: string;
23
+ /** Consumer-supplied kit Buttons or native links. No built-in actions. */
24
+ actions?: ReactNode;
25
+ defaultCycle?: BillingCycle;
26
+ onCycleChange?: (cycle: BillingCycle) => void;
27
+ titleAs?: 'h2' | 'h3' | 'h4';
28
+ className?: string;
29
+ }
30
+
31
+ export function PlanSummary({
32
+ plan,
33
+ emptyLabel,
34
+ actions,
35
+ defaultCycle = 'monthly',
36
+ onCycleChange,
37
+ titleAs: TitleTag = 'h3',
38
+ className,
39
+ }: PlanSummaryProps) {
40
+ const title = plan?.name || emptyLabel;
41
+ const [cycle, setCycle] = useState<BillingCycle>(defaultCycle);
42
+ const hasBothPrices = plan?.priceMonthly != null && plan?.priceAnnual != null;
43
+ const price = hasBothPrices
44
+ ? cycle === 'annual'
45
+ ? plan.priceAnnual
46
+ : plan.priceMonthly
47
+ : (plan?.priceMonthly ?? plan?.priceAnnual);
48
+ const changeCycle = (values: string[]) => {
49
+ const next = values[0];
50
+ if (next !== 'monthly' && next !== 'annual') return;
51
+ setCycle(next);
52
+ onCycleChange?.(next);
53
+ };
54
+ useEffect(() => {
55
+ if (!title && process.env.NODE_ENV === 'development') {
56
+ console.warn('PlanCard summary: supply a plan name or emptyLabel.');
57
+ }
58
+ }, [title]);
59
+ return (
60
+ <Card className={cn('p-6', className)}>
61
+ <div className="flex flex-wrap items-center gap-2">
62
+ {title && <TitleTag className="text-lg font-semibold text-foreground">{title}</TitleTag>}
63
+ {plan?.statusLabel && (
64
+ <Badge tone={plan.statusTone ?? 'neutral'} dot>
65
+ {plan.statusLabel}
66
+ </Badge>
67
+ )}
68
+ </div>
69
+ {plan?.renewsOn != null && (
70
+ <p className="mt-1 text-sm text-muted-foreground">Renews {formatDate(plan.renewsOn)}</p>
71
+ )}
72
+ {price != null && (
73
+ <p className="mt-4 text-3xl font-semibold text-foreground tabular-nums">
74
+ {formatMoney(price, plan?.currency, true)}
75
+ </p>
76
+ )}
77
+ {plan?.cycle && (
78
+ <p className="mt-1 text-sm text-muted-foreground">
79
+ {(hasBothPrices ? cycle : plan.cycle) === 'annual' ? 'Billed annually' : 'Billed monthly'}
80
+ </p>
81
+ )}
82
+ {hasBothPrices && (
83
+ <ToggleGroup className="mt-4" size="sm" aria-label="Billing cycle" value={[cycle]} onValueChange={changeCycle}>
84
+ <Toggle value="monthly">Monthly</Toggle>
85
+ <Toggle value="annual">Annual</Toggle>
86
+ </ToggleGroup>
87
+ )}
88
+ {plan?.features && plan.features.length > 0 && (
89
+ <ul
90
+ aria-label="Plan features"
91
+ className="mt-5 grid gap-2 border-t border-border pt-5 text-sm text-foreground sm:grid-cols-2"
92
+ >
93
+ {plan.features.map((feature) => (
94
+ <li key={feature}>{feature}</li>
95
+ ))}
96
+ </ul>
97
+ )}
98
+ {actions && <div className="mt-4 flex flex-wrap items-center gap-2">{actions}</div>}
99
+ </Card>
100
+ );
101
+ }
@@ -0,0 +1,23 @@
1
+ # Plan card
2
+
3
+ `PlanCard` defaults to the existing billing header. Opt into `variant="summary"` for consumer-only data with independently optional regions. Import the component and types from `@gntik-ai/blocks`.
4
+
5
+ | Props | Billing (default) | Summary |
6
+ | ------------------------------- | --------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
7
+ | `plan` | `BillingPlan`; omitted uses `samplePlan` | `SummaryPlan`; omitted renders no sample values |
8
+ | Plan name | Required; adds “plan” to the heading | Optional, shown verbatim; missing name uses `emptyLabel` |
9
+ | Status | Optional enum; falls back to Active | Optional `statusLabel` and `statusTone`; no default label |
10
+ | Prices | Required `priceMonthly`; optional `priceAnnual` is a discounted monthly amount | Optional `priceMonthly` and `priceAnnual` are complete cycle totals; zero is valid |
11
+ | `currency` | Defaults to USD | Formatting defaults to USD only when a price is supplied |
12
+ | Cycle | Always renders billing copy; annual total is monthly amount × 12 | Optional `plan.cycle` renders cycle copy; toggle only when both prices are supplied, with no savings arithmetic |
13
+ | `renewsOn` | Required; always shows formatted renewal | Optional; absent means no renewal line |
14
+ | `features` | Optional nonempty array renders a list | Optional nonempty array renders a list |
15
+ | Actions | Always renders Manage and Upgrade plan buttons; `onManage`, `onUpgrade`, `manageLabel`, `upgradeLabel` customize them | Optional `actions: ReactNode` slot of kit Buttons or native links; no default actions |
16
+ | `defaultCycle`, `onCycleChange` | Initial cycle (monthly by default), selection callback | Same; applies to the optional toggle |
17
+ | `titleAs`, `className` | h2/h3/h4 (default h3), token-backed classes | Same |
18
+
19
+ Billing also supports optional `plan.summary` after renewal. Summary never imports sample data. With neither a name nor an `emptyLabel`, summary renders no heading and warns only in development.
20
+
21
+ ARIA and keyboard: a heading at the consumer's requested level, a visibly labelled status badge, a named features list, and native consumer actions. The cycle group is named “Billing cycle”; toggles expose `aria-pressed`. Tab/Shift+Tab visit controls; arrow keys/Home/End navigate the toggle; Enter/Space activate buttons and toggles; Enter activates links. With fewer than two prices the toggle is absent from both DOM and tab order. Axe tests cover dark, light and high_contrast; browser tests also check contrast.
22
+
23
+ Preview IDs: `plan-card--summary-full`, `--summary-minimal`, `--summary-unassigned`, `--summary-unassigned-no-label`, `--summary-price-only`, `--summary-zero-price`, `--summary-renewal-only`, `--summary-features-only`, `--summary-actions-only`. Examples use English copy; translated values belong to consumers.
@@ -0,0 +1,5 @@
1
+ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
+
3
+ exports[`PlanCard billing DOM baseline > preserves the FULL billing header 1`] = `"<div><div class="overflow-hidden rounded-xl border border-border text-card-foreground bg-card shadow-sm p-6"><div class="flex flex-col gap-5 sm:flex-row sm:items-start sm:justify-between"><div class="min-w-0"><div class="flex flex-wrap items-center gap-2"><h3 class="text-[17px] font-semibold tracking-tight text-foreground">Fixture Plan plan</h3><span class="inline-flex max-w-full shrink-0 items-center gap-1.5 font-semibold whitespace-nowrap select-none h-chip px-2.5 font-mono text-[10.5px] rounded-md bg-success/15 text-success-chip-text"><span aria-hidden="true" class="size-1.5 shrink-0 rounded-full bg-success"></span>Active</span></div><p class="mt-1 text-[13px] text-muted-foreground">Renews January 1, 2027 · 2 seats</p><p class="mt-4 flex items-baseline gap-1.5"><span class="text-[30px] font-semibold tracking-tight text-foreground tabular-nums">€10</span><span class="text-[13px] text-muted-foreground">/ month</span></p><p class="mt-1 text-[12px] text-muted-foreground">Billed monthly</p></div><div class="flex flex-col items-start gap-3 sm:items-end"><div data-orientation="horizontal" role="group" aria-label="Billing cycle" class="inline-flex data-[orientation=vertical]:flex-col data-disabled:opacity-50 items-center gap-0.5 rounded-lg border border-border bg-card p-0.5 shadow-sm"><button type="button" data-pressed="" aria-disabled="false" tabindex="0" aria-pressed="true" class="inline-flex shrink-0 items-center justify-center gap-2 font-medium whitespace-nowrap text-muted-foreground transition-colors select-none hover:text-foreground data-pressed:bg-secondary data-pressed:text-foreground focus-visible:relative focus-visible:z-10 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus-ring data-disabled:cursor-not-allowed data-disabled:opacity-50 data-disabled:hover:text-muted-foreground rounded-md data-pressed:shadow-sm h-7 px-2.5 text-[12px]">Monthly</button><button type="button" aria-disabled="false" tabindex="-1" aria-pressed="false" class="inline-flex shrink-0 items-center justify-center gap-2 font-medium whitespace-nowrap text-muted-foreground transition-colors select-none hover:text-foreground data-pressed:bg-secondary data-pressed:text-foreground focus-visible:relative focus-visible:z-10 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus-ring data-disabled:cursor-not-allowed data-disabled:opacity-50 data-disabled:hover:text-muted-foreground rounded-md data-pressed:shadow-sm h-7 px-2.5 text-[12px]">Annual</button></div><div class="flex flex-wrap items-center gap-2"><button type="button" tabindex="0" class="inline-flex shrink-0 items-center justify-center font-semibold whitespace-nowrap transition-colors select-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus-ring data-[disabled]:pointer-events-none data-[disabled]:opacity-50 border border-border bg-card text-foreground shadow-sm hover:bg-secondary/70 h-control gap-1.5 px-control text-[13px] rounded-lg">Manage</button><button type="button" tabindex="0" class="inline-flex shrink-0 items-center justify-center font-semibold whitespace-nowrap transition-colors select-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus-ring data-[disabled]:pointer-events-none data-[disabled]:opacity-50 bg-primary text-primary-foreground shadow-sm hover:bg-primary/90 h-control gap-1.5 px-control text-[13px] rounded-lg"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-sparkles lucide-stars" aria-hidden="true"><path d="M11.017 2.814a1 1 0 0 1 1.966 0l1.051 5.558a2 2 0 0 0 1.594 1.594l5.558 1.051a1 1 0 0 1 0 1.966l-5.558 1.051a2 2 0 0 0-1.594 1.594l-1.051 5.558a1 1 0 0 1-1.966 0l-1.051-5.558a2 2 0 0 0-1.594-1.594l-5.558-1.051a1 1 0 0 1 0-1.966l5.558-1.051a2 2 0 0 0 1.594-1.594z"></path><path d="M20 2v4"></path><path d="M22 4h-4"></path><circle cx="4" cy="20" r="2"></circle></svg>Upgrade</button></div></div></div><ul aria-label="Fixture Plan plan features" class="mt-5 grid gap-x-6 gap-y-2 border-t border-border pt-5 sm:grid-cols-2"><li class="flex items-start gap-2 text-[13px] text-foreground"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-check mt-0.5 shrink-0 text-primary-text" aria-hidden="true"><path d="M20 6 9 17l-5-5"></path></svg>Feature A</li><li class="flex items-start gap-2 text-[13px] text-foreground"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-check mt-0.5 shrink-0 text-primary-text" aria-hidden="true"><path d="M20 6 9 17l-5-5"></path></svg>Feature B</li></ul></div></div>"`;
4
+
5
+ exports[`PlanCard billing DOM baseline > preserves the no-props billing header 1`] = `"<div><div class="overflow-hidden rounded-xl border border-border text-card-foreground bg-card shadow-sm p-6"><div class="flex flex-col gap-5 sm:flex-row sm:items-start sm:justify-between"><div class="min-w-0"><div class="flex flex-wrap items-center gap-2"><h3 class="text-[17px] font-semibold tracking-tight text-foreground">Scale plan</h3><span class="inline-flex max-w-full shrink-0 items-center gap-1.5 font-semibold whitespace-nowrap select-none h-chip px-2.5 font-mono text-[10.5px] rounded-md bg-success/15 text-success-chip-text"><span aria-hidden="true" class="size-1.5 shrink-0 rounded-full bg-success"></span>Active</span></div><p class="mt-1 text-[13px] text-muted-foreground">Renews June 1, 2026 · 50 seats · 2 regions</p><p class="mt-4 flex items-baseline gap-1.5"><span class="text-[30px] font-semibold tracking-tight text-foreground tabular-nums">$1,200</span><span class="text-[13px] text-muted-foreground">/ month</span></p><p class="mt-1 text-[12px] text-muted-foreground">Billed monthly</p></div><div class="flex flex-col items-start gap-3 sm:items-end"><div data-orientation="horizontal" role="group" aria-label="Billing cycle" class="inline-flex data-[orientation=vertical]:flex-col data-disabled:opacity-50 items-center gap-0.5 rounded-lg border border-border bg-card p-0.5 shadow-sm"><button type="button" data-pressed="" aria-disabled="false" tabindex="0" aria-pressed="true" class="inline-flex shrink-0 items-center justify-center gap-2 font-medium whitespace-nowrap text-muted-foreground transition-colors select-none hover:text-foreground data-pressed:bg-secondary data-pressed:text-foreground focus-visible:relative focus-visible:z-10 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus-ring data-disabled:cursor-not-allowed data-disabled:opacity-50 data-disabled:hover:text-muted-foreground rounded-md data-pressed:shadow-sm h-7 px-2.5 text-[12px]">Monthly</button><button type="button" aria-disabled="false" tabindex="-1" aria-pressed="false" class="inline-flex shrink-0 items-center justify-center gap-2 font-medium whitespace-nowrap text-muted-foreground transition-colors select-none hover:text-foreground data-pressed:bg-secondary data-pressed:text-foreground focus-visible:relative focus-visible:z-10 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus-ring data-disabled:cursor-not-allowed data-disabled:opacity-50 data-disabled:hover:text-muted-foreground rounded-md data-pressed:shadow-sm h-7 px-2.5 text-[12px]">Annual −17%</button></div><div class="flex flex-wrap items-center gap-2"><button type="button" tabindex="0" class="inline-flex shrink-0 items-center justify-center font-semibold whitespace-nowrap transition-colors select-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus-ring data-[disabled]:pointer-events-none data-[disabled]:opacity-50 border border-border bg-card text-foreground shadow-sm hover:bg-secondary/70 h-control gap-1.5 px-control text-[13px] rounded-lg">Manage</button><button type="button" tabindex="0" class="inline-flex shrink-0 items-center justify-center font-semibold whitespace-nowrap transition-colors select-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus-ring data-[disabled]:pointer-events-none data-[disabled]:opacity-50 bg-primary text-primary-foreground shadow-sm hover:bg-primary/90 h-control gap-1.5 px-control text-[13px] rounded-lg"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-sparkles lucide-stars" aria-hidden="true"><path d="M11.017 2.814a1 1 0 0 1 1.966 0l1.051 5.558a2 2 0 0 0 1.594 1.594l5.558 1.051a1 1 0 0 1 0 1.966l-5.558 1.051a2 2 0 0 0-1.594 1.594l-1.051 5.558a1 1 0 0 1-1.966 0l-1.051-5.558a2 2 0 0 0-1.594-1.594l-5.558-1.051a1 1 0 0 1 0-1.966l5.558-1.051a2 2 0 0 0 1.594-1.594z"></path><path d="M20 2v4"></path><path d="M22 4h-4"></path><circle cx="4" cy="20" r="2"></circle></svg>Upgrade plan</button></div></div></div><ul aria-label="Scale plan features" class="mt-5 grid gap-x-6 gap-y-2 border-t border-border pt-5 sm:grid-cols-2"><li class="flex items-start gap-2 text-[13px] text-foreground"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-check mt-0.5 shrink-0 text-primary-text" aria-hidden="true"><path d="M20 6 9 17l-5-5"></path></svg>500,000 requests / month</li><li class="flex items-start gap-2 text-[13px] text-foreground"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-check mt-0.5 shrink-0 text-primary-text" aria-hidden="true"><path d="M20 6 9 17l-5-5"></path></svg>120M tokens / month</li><li class="flex items-start gap-2 text-[13px] text-foreground"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-check mt-0.5 shrink-0 text-primary-text" aria-hidden="true"><path d="M20 6 9 17l-5-5"></path></svg>20 GB storage</li><li class="flex items-start gap-2 text-[13px] text-foreground"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-check mt-0.5 shrink-0 text-primary-text" aria-hidden="true"><path d="M20 6 9 17l-5-5"></path></svg>Audit log and SSO</li><li class="flex items-start gap-2 text-[13px] text-foreground"><svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-check mt-0.5 shrink-0 text-primary-text" aria-hidden="true"><path d="M20 6 9 17l-5-5"></path></svg>Email support · 1 business day</li></ul></div></div>"`;
@@ -0,0 +1,60 @@
1
+ import type { BlockMeta } from '../../meta';
2
+
3
+ /** Catalogue contract read by get_block and llms-full.txt. */
4
+ export const documentation = {
5
+ pattern:
6
+ 'Heading (titleAs h2/h3/h4), text-labelled Badge, optional features list and native consumer actions. Summary omits absent regions; no heading plus a development warning when name and emptyLabel are absent. ToggleGroup has an accessible Billing cycle label and aria-pressed buttons, rendered only with both prices. Axe coverage in dark, light and high_contrast.',
7
+ keyboard: [
8
+ ['Tab / Shift+Tab', 'Move through the cycle toggle (only with both prices) and supplied buttons or links.'],
9
+ ['Arrow keys / Home / End', 'Move focus within the monthly/annual toggle group.'],
10
+ ['Enter / Space', 'Activate focused buttons and toggles; Enter activates links.'],
11
+ ],
12
+ tokens: ['card', 'foreground', 'muted-foreground', 'border', 'focus-ring'],
13
+ props: [
14
+ ['variant', 'billing (default) | summary'],
15
+ [
16
+ 'billing plan',
17
+ 'BillingPlan: name, priceMonthly and renewsOn required; status, priceAnnual, currency, summary and features optional. Omitted plan uses samplePlan.',
18
+ ],
19
+ [
20
+ 'summary plan',
21
+ 'SummaryPlan: all optional: name, statusLabel, statusTone, priceMonthly, priceAnnual, currency, cycle, renewsOn, features. Prices are supplied cycle totals, never multiplied or discounted.',
22
+ ],
23
+ ['emptyLabel', 'Summary only: consumer label used when the name is absent.'],
24
+ ['actions', 'Summary only: ReactNode slot of kit Buttons or native links, absent by default.'],
25
+ ['defaultCycle / onCycleChange', 'Initial monthly (default) or annual selection and cycle callback.'],
26
+ ['titleAs / className', 'h2 | h3 (default) | h4; additional token-backed classes.'],
27
+ [
28
+ 'onUpgrade / onManage / upgradeLabel / manageLabel',
29
+ 'Billing only: existing action handlers and labels; both buttons always render.',
30
+ ],
31
+ ],
32
+ examples: [
33
+ 'summary-full',
34
+ 'summary-minimal',
35
+ 'summary-unassigned',
36
+ 'summary-unassigned-no-label',
37
+ 'summary-price-only',
38
+ 'summary-zero-price',
39
+ 'summary-renewal-only',
40
+ 'summary-features-only',
41
+ 'summary-actions-only',
42
+ ],
43
+ };
44
+
45
+ export const meta: BlockMeta = {
46
+ name: 'Plan card',
47
+ family: 'billing',
48
+ status: 'beta',
49
+ description: [
50
+ 'Default billing header retains sample plan, Active status fallback, required monthly price and renewal, monthly/annual per-month pricing and built-in Manage/Upgrade actions. Opt-in variant="summary" uses only consumer data: optional name or emptyLabel, statusLabel, price totals (including zero), cycle, renewal, features and action slots; no sample fallback. See README.md for props, ARIA, keyboard and examples.',
51
+ '',
52
+ '| Prop | Contract |',
53
+ '| --- | --- |',
54
+ ...documentation.props.map(([prop, contract]) => `| ${prop} | ${contract?.replaceAll('|', '\\|')} |`),
55
+ '',
56
+ 'Examples:',
57
+ ...documentation.examples.map((example) => `- [${example}](/preview.html?kind=block&id=plan-card--${example})`),
58
+ ].join('\n'),
59
+ uses: ['Card', 'Badge', 'Button', 'ToggleGroup', 'Toggle'],
60
+ };