@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,226 @@
1
+ import {
2
+ Alert,
3
+ Button,
4
+ Checkbox,
5
+ Field,
6
+ FieldDescription,
7
+ FieldError,
8
+ FieldLabel,
9
+ Input,
10
+ Link,
11
+ PasswordInput,
12
+ cn,
13
+ } from '@gntik-ai/ui';
14
+ import { Lock, Mail } from '@gntik-ai/icons';
15
+ import { useImperativeHandle, useRef, useState, type MouseEvent, type ReactNode, type Ref } from 'react';
16
+ import { SsoButtons } from '../SsoButtons/SsoButtons';
17
+ import {
18
+ AuthDivider,
19
+ AuthHeader,
20
+ authIdentifierError,
21
+ authFormClass,
22
+ useAsyncSubmit,
23
+ type AuthIdentifierConfig,
24
+ type AuthValidationMessages,
25
+ type AuthFormAccessibilityProps,
26
+ type AuthHeaderProps,
27
+ } from '../shared';
28
+
29
+ export interface SignInValues {
30
+ email: string;
31
+ /** Trimmed identifier in text mode. `email` remains available for existing consumers. */
32
+ identifier?: string;
33
+ password: string;
34
+ remember: boolean;
35
+ }
36
+
37
+ export interface SignInFormRef {
38
+ focus(field: 'identifier' | 'password'): void;
39
+ }
40
+
41
+ export interface SignInFormProps extends AuthFormAccessibilityProps {
42
+ /** React 19 ref for focus after a server response. */
43
+ ref?: Ref<SignInFormRef>;
44
+ identifier?: AuthIdentifierConfig;
45
+ /** The kit validates before submit. Supply copy here; do not add a second inline validator. */
46
+ messages?: AuthValidationMessages;
47
+ passwordHelp?: ReactNode;
48
+ passwordHelpId?: string;
49
+ /** Signs in. A returned promise shows the loading state; a rejection's message shows in the alert. */
50
+ onSubmit?: (values: SignInValues) => void | Promise<void>;
51
+ /** Error from the server (e.g. "Incorrect email or password"). */
52
+ error?: string | null;
53
+ /** Forces the loading state (when the parent tracks the request). */
54
+ loading?: boolean;
55
+ defaultEmail?: string;
56
+ forgotPasswordHref?: string;
57
+ onForgotPassword?: () => void;
58
+ signUpHref?: string;
59
+ onSignUp?: () => void;
60
+ /** Replaces the sign-up prompt; `null` hides it. Defaults to the existing account link. */
61
+ signUpPrompt?: ReactNode;
62
+ /** SSO area under the divider; defaults to SsoButtons, `null` hides it. */
63
+ sso?: ReactNode;
64
+ eyebrow?: ReactNode;
65
+ title?: ReactNode;
66
+ description?: ReactNode;
67
+ /** Omit the internal heading with null; the form keeps its accessible name. */
68
+ headingLevel?: AuthHeaderProps['headingLevel'];
69
+ className?: string;
70
+ }
71
+
72
+ const linkHandler = (fn?: () => void) =>
73
+ fn
74
+ ? (e: MouseEvent) => {
75
+ e.preventDefault();
76
+ fn();
77
+ }
78
+ : undefined;
79
+
80
+ /** Email + password sign-in with remember me, forgot-password link, SSO slot, error alert and loading. */
81
+ export function SignInForm({
82
+ identifier,
83
+ messages,
84
+ passwordHelp,
85
+ passwordHelpId,
86
+ ref,
87
+ fieldInvalid,
88
+ feedbackId,
89
+ 'aria-describedby': describedBy,
90
+ 'aria-label': ariaLabel = 'Sign in',
91
+ 'aria-labelledby': labelledBy,
92
+ onSubmit,
93
+ error: errorProp,
94
+ loading: loadingProp,
95
+ defaultEmail = '',
96
+ forgotPasswordHref = '#',
97
+ onForgotPassword,
98
+ signUpHref = '#',
99
+ onSignUp,
100
+ signUpPrompt,
101
+ sso = <SsoButtons />,
102
+ eyebrow = 'Secure access',
103
+ title = 'Sign in to your workspace',
104
+ description = 'Use your work email to continue.',
105
+ headingLevel,
106
+ className,
107
+ }: SignInFormProps) {
108
+ const [email, setEmail] = useState(defaultEmail);
109
+ const [password, setPassword] = useState('');
110
+ const [remember, setRemember] = useState(true);
111
+ const [submitted, setSubmitted] = useState(false);
112
+ const { loading, error, run } = useAsyncSubmit('Sign-in failed. Try again.');
113
+ const busy = loadingProp ?? loading;
114
+ const alert = errorProp ?? error;
115
+ const identifierInput = useRef<HTMLInputElement>(null);
116
+ const passwordInput = useRef<HTMLInputElement>(null);
117
+ useImperativeHandle(
118
+ ref,
119
+ () => ({
120
+ focus: (field) => (field === 'password' ? passwordInput : identifierInput).current?.focus(),
121
+ }),
122
+ [],
123
+ );
124
+ const identifierError = authIdentifierError(email, identifier, messages);
125
+ const emailInvalid = submitted && identifierError !== null;
126
+ const passwordInvalid = submitted && password === '';
127
+
128
+ return (
129
+ <div className={cn(authFormClass, className)}>
130
+ <AuthHeader eyebrow={eyebrow} title={title} description={description} headingLevel={headingLevel} />
131
+ <form
132
+ noValidate
133
+ aria-label={ariaLabel}
134
+ aria-labelledby={labelledBy}
135
+ aria-describedby={describedBy}
136
+ className="flex flex-col gap-4"
137
+ onSubmit={(e) => {
138
+ e.preventDefault();
139
+ setSubmitted(true);
140
+ if (identifierError !== null) {
141
+ identifierInput.current?.focus();
142
+ return;
143
+ }
144
+ if (password === '') {
145
+ passwordInput.current?.focus();
146
+ return;
147
+ }
148
+ void run(() =>
149
+ onSubmit?.({
150
+ email: email.trim(),
151
+ ...(identifier?.type === 'text' ? { identifier: email.trim() } : {}),
152
+ password,
153
+ remember,
154
+ }),
155
+ );
156
+ }}
157
+ >
158
+ {alert && <Alert id={feedbackId} tone="destructive" description={alert} />}
159
+ <Field invalid={emailInvalid || fieldInvalid?.identifier === true}>
160
+ <FieldLabel>{identifier?.label ?? 'Email'}</FieldLabel>
161
+ <Input
162
+ ref={identifierInput}
163
+ aria-describedby={fieldInvalid?.identifier ? feedbackId : undefined}
164
+ type={identifier?.type ?? 'email'}
165
+ leadingIcon={Mail}
166
+ autoComplete={identifier?.autoComplete ?? 'email'}
167
+ placeholder={identifier?.placeholder ?? 'name@company.com'}
168
+ minLength={identifier?.minLength}
169
+ maxLength={identifier?.maxLength}
170
+ value={email}
171
+ onValueChange={setEmail}
172
+ required
173
+ />
174
+ {identifier?.help != null && <FieldDescription id={identifier.helpId}>{identifier.help}</FieldDescription>}
175
+ <FieldError role="alert" match={emailInvalid}>
176
+ {identifierError}
177
+ </FieldError>
178
+ </Field>
179
+ <Field invalid={passwordInvalid || fieldInvalid?.password === true}>
180
+ <div className="flex items-center justify-between gap-3">
181
+ <FieldLabel>Password</FieldLabel>
182
+ <Link href={forgotPasswordHref} onClick={linkHandler(onForgotPassword)} className="text-[12.5px]">
183
+ Forgot password?
184
+ </Link>
185
+ </div>
186
+ <PasswordInput
187
+ ref={passwordInput}
188
+ aria-describedby={fieldInvalid?.password ? feedbackId : undefined}
189
+ leadingIcon={Lock}
190
+ value={password}
191
+ onValueChange={setPassword}
192
+ required
193
+ />
194
+ {passwordHelp != null && <FieldDescription id={passwordHelpId}>{passwordHelp}</FieldDescription>}
195
+ <FieldError role="alert" match={passwordInvalid}>
196
+ {messages?.passwordRequired ?? 'Enter your password.'}
197
+ </FieldError>
198
+ </Field>
199
+ <Checkbox label="Keep me signed in" checked={remember} onCheckedChange={setRemember} />
200
+ <Button type="submit" size="lg" loading={busy} className="mt-2 w-full">
201
+ Sign in
202
+ </Button>
203
+ </form>
204
+ {sso != null && (
205
+ <>
206
+ <AuthDivider>Or continue with</AuthDivider>
207
+ {sso}
208
+ </>
209
+ )}
210
+ {signUpPrompt !== null && (
211
+ <p className="mt-7 text-center text-[13px] text-muted-foreground">
212
+ {signUpPrompt === undefined ? (
213
+ <>
214
+ New here?{' '}
215
+ <Link href={signUpHref} onClick={linkHandler(onSignUp)}>
216
+ Create an account
217
+ </Link>
218
+ </>
219
+ ) : (
220
+ signUpPrompt
221
+ )}
222
+ </p>
223
+ )}
224
+ </div>
225
+ );
226
+ }
@@ -0,0 +1,22 @@
1
+ import type { BlockMeta } from '../../meta';
2
+
3
+ export const meta: BlockMeta = {
4
+ name: 'Sign-in form',
5
+ family: 'auth',
6
+ status: 'stable',
7
+ description:
8
+ 'Email (default) or text identifier and password sign-in. Supports identifier (type, autoComplete, label, help/helpId, placeholder, minLength/maxLength), messages (identifierRequired/identifierInvalid/identifierTooShort/passwordRequired), and React 19 ref.focus. The kit owns inline validation and focuses the first invalid field; consumers supply messages, not a second inline validator. Server errors use error + fieldInvalid + feedbackId; aria-describedby describes the form, while flagged inputs also retain their help and error ids. headingLevel (h1/h2/h3/null) suppresses the heading without losing the form name; aria-label/aria-labelledby override the default name. passwordHelp/passwordHelpId describe the password. Text submissions add values.identifier and retain values.email; email-mode payloads are unchanged. sso and signUpPrompt accept replacements or null. Preview: sign-in-form--username.',
9
+ uses: [
10
+ 'Field',
11
+ 'FieldLabel',
12
+ 'FieldDescription',
13
+ 'FieldError',
14
+ 'Input',
15
+ 'PasswordInput',
16
+ 'Checkbox',
17
+ 'Button',
18
+ 'Alert',
19
+ 'Link',
20
+ 'SsoButtons',
21
+ ],
22
+ };
@@ -0,0 +1,26 @@
1
+ import { SignInForm, type SignInFormProps } from '../SignInForm';
2
+
3
+ /** /preview.html?kind=block&id=sign-in-form--username */
4
+ export default function Username(props: Pick<SignInFormProps, 'headingLevel'>) {
5
+ return (
6
+ <SignInForm
7
+ {...props}
8
+ identifier={{
9
+ type: 'text',
10
+ autoComplete: 'username',
11
+ label: 'Username',
12
+ help: 'Use your account name.',
13
+ placeholder: 'Account name',
14
+ minLength: 3,
15
+ maxLength: 120,
16
+ }}
17
+ messages={{
18
+ identifierRequired: 'Enter your username.',
19
+ identifierInvalid: 'Use at least three characters.',
20
+ passwordRequired: 'Enter your password.',
21
+ }}
22
+ description="Use your account name to continue."
23
+ sso={null}
24
+ />
25
+ );
26
+ }
@@ -0,0 +1,316 @@
1
+ import { render, screen, within, waitFor } from '@testing-library/react';
2
+ import userEvent from '@testing-library/user-event';
3
+ import { expectNoAxeViolations } from '../../test/a11y';
4
+ import { SignUpForm } from './SignUpForm';
5
+
6
+ const fields = [
7
+ {
8
+ id: 'username',
9
+ label: 'Username',
10
+ placeholder: 'Account name',
11
+ help: 'Choose an account name.',
12
+ required: true,
13
+ type: 'text' as const,
14
+ autoComplete: 'username',
15
+ },
16
+ {
17
+ id: 'displayName',
18
+ label: 'Display name',
19
+ placeholder: 'Your name',
20
+ help: 'Name shown to others.',
21
+ required: true,
22
+ type: 'text' as const,
23
+ autoComplete: 'name',
24
+ },
25
+ {
26
+ id: 'email',
27
+ label: 'Email',
28
+ placeholder: 'you@example.com',
29
+ help: 'For account notifications.',
30
+ required: true,
31
+ type: 'email' as const,
32
+ autoComplete: 'email',
33
+ },
34
+ {
35
+ id: 'tenant',
36
+ label: 'Tenant identifier',
37
+ placeholder: 'Tenant',
38
+ help: 'Your organization.',
39
+ required: true,
40
+ type: 'text' as const,
41
+ autoComplete: 'off',
42
+ },
43
+ {
44
+ id: 'workspace',
45
+ label: 'Workspace identifier',
46
+ placeholder: 'Workspace',
47
+ help: 'Optional workspace.',
48
+ required: false,
49
+ type: 'text' as const,
50
+ autoComplete: 'off',
51
+ },
52
+ ];
53
+
54
+ describe('SignUpForm configured fields', () => {
55
+ it('blocks whitespace-only required fields independently of password and consent, and refocuses the summary on retry', async () => {
56
+ const onSubmit = vi.fn();
57
+ render(
58
+ <SignUpForm
59
+ fields={[
60
+ {
61
+ id: 'username',
62
+ label: 'Username',
63
+ required: true,
64
+ help: 'Choose an account name.',
65
+ messages: { required: 'An account name is required.' },
66
+ },
67
+ { id: 'workspace', label: 'Workspace identifier' },
68
+ ]}
69
+ terms={false}
70
+ sso={null}
71
+ onSubmit={onSubmit}
72
+ />,
73
+ );
74
+ await userEvent.type(screen.getByLabelText('Username'), ' ');
75
+ await userEvent.type(screen.getByLabelText('Password'), 'correct-horse-42');
76
+ await userEvent.click(screen.getByRole('button', { name: 'Create account' }));
77
+ expect(onSubmit).not.toHaveBeenCalled();
78
+ const summary = screen.getByRole('region', { name: 'There is 1 problem with this form' });
79
+ expect(summary).toHaveFocus();
80
+ await userEvent.tab();
81
+ expect(within(summary).getByRole('link', { name: 'Username: An account name is required.' })).toHaveFocus();
82
+ await userEvent.keyboard('{Enter}');
83
+ expect(screen.getByLabelText('Username')).toHaveFocus();
84
+ expect(screen.getByLabelText('Username')).toHaveAccessibleDescription(
85
+ 'Choose an account name. An account name is required.',
86
+ );
87
+ await userEvent.click(screen.getByRole('button', { name: 'Create account' }));
88
+ expect(summary).toHaveFocus();
89
+ expect(onSubmit).not.toHaveBeenCalled();
90
+ });
91
+
92
+ it('renders ordered fields, links all errors to inputs, and submits trimmed values with an empty optional field', async () => {
93
+ const onSubmit = vi.fn();
94
+ const { container } = render(<SignUpForm fields={fields} onSubmit={onSubmit} sso={null} />);
95
+ expect(
96
+ Array.from(container.querySelectorAll('input[type="text"], input[type="email"], input[type="password"]')).map(
97
+ (input) => input.getAttribute('placeholder'),
98
+ ),
99
+ ).toEqual(['Account name', 'Your name', 'you@example.com', 'Tenant', 'Workspace', null]);
100
+ for (const field of fields) {
101
+ const input = screen.getByLabelText(field.label);
102
+ expect(input).toHaveAttribute('type', field.type);
103
+ expect(input).toHaveAttribute('autocomplete', field.autoComplete);
104
+ expect(input).toHaveAccessibleDescription(field.help);
105
+ expect(input).toHaveProperty('required', field.required);
106
+ }
107
+ await expectNoAxeViolations(container);
108
+ await userEvent.click(screen.getByRole('button', { name: 'Create account' }));
109
+ expect(onSubmit).not.toHaveBeenCalled();
110
+ const summary = screen.getByRole('region');
111
+ expect(summary).toHaveFocus();
112
+ expect(within(summary).getAllByRole('link')).toHaveLength(6);
113
+ await userEvent.click(within(summary).getByRole('link', { name: 'Username: Enter Username.' }));
114
+ expect(screen.getByLabelText('Username')).toHaveFocus();
115
+ expect(screen.getByLabelText('Username')).toHaveAttribute('aria-invalid', 'true');
116
+ expect(screen.getByLabelText('Username')).toHaveAccessibleDescription('Choose an account name. Enter Username.');
117
+ await expectNoAxeViolations(container);
118
+ await userEvent.type(screen.getByLabelText('Username'), ' avery ');
119
+ await userEvent.type(screen.getByLabelText('Display name'), ' Avery Collins ');
120
+ await userEvent.type(screen.getByLabelText('Email'), ' avery@example.com ');
121
+ await userEvent.type(screen.getByLabelText('Tenant identifier'), ' team ');
122
+ await userEvent.type(screen.getByLabelText('Password'), 'correct-horse-42');
123
+ await userEvent.click(screen.getByRole('checkbox'));
124
+ await userEvent.click(screen.getByRole('button', { name: 'Create account' }));
125
+ expect(onSubmit).toHaveBeenCalledWith({
126
+ name: '',
127
+ email: 'avery@example.com',
128
+ password: 'correct-horse-42',
129
+ fields: {
130
+ username: 'avery',
131
+ displayName: 'Avery Collins',
132
+ email: 'avery@example.com',
133
+ tenant: 'team',
134
+ workspace: '',
135
+ },
136
+ });
137
+ });
138
+ });
139
+
140
+ it('omits consent only when terms is explicitly false, in default and configured modes', async () => {
141
+ const onSubmit = vi.fn();
142
+ const { rerender } = render(<SignUpForm terms={false} sso={null} onSubmit={onSubmit} />);
143
+ expect(screen.queryByRole('checkbox')).not.toBeInTheDocument();
144
+ await userEvent.type(screen.getByLabelText('Full name'), ' Avery ');
145
+ await userEvent.type(screen.getByLabelText('Work email'), 'avery@example.com');
146
+ await userEvent.type(screen.getByLabelText('Password'), 'correct-horse-42');
147
+ await userEvent.click(screen.getByRole('button', { name: 'Create account' }));
148
+ expect(onSubmit).toHaveBeenCalledWith({ name: 'Avery', email: 'avery@example.com', password: 'correct-horse-42' });
149
+ onSubmit.mockClear();
150
+ rerender(<SignUpForm fields={[]} terms={false} sso={null} onSubmit={onSubmit} />);
151
+ await userEvent.click(screen.getByRole('button', { name: 'Create account' }));
152
+ expect(onSubmit).toHaveBeenCalledWith({ name: '', email: '', password: 'correct-horse-42', fields: {} });
153
+ });
154
+
155
+ it.each([undefined, 8, 12])(
156
+ 'uses the password minimum %s for inline validation, optional help and the meter',
157
+ async (minimum) => {
158
+ const onSubmit = vi.fn();
159
+ const min = minimum ?? 8;
160
+ const { container } = render(
161
+ <SignUpForm
162
+ fields={[]}
163
+ terms={false}
164
+ minPasswordLength={minimum}
165
+ passwordHelp={`Use at least ${min} characters to secure your account.`}
166
+ onSubmit={onSubmit}
167
+ sso={null}
168
+ />,
169
+ );
170
+ const password = screen.getByLabelText('Password');
171
+ expect(password).toHaveAccessibleDescription(
172
+ expect.stringContaining(`Use at least ${min} characters to secure your account.`),
173
+ );
174
+ await userEvent.type(password, min === 8 ? 'Abc1!xy' : 'Abc1!xyzabc');
175
+ expect(password).toHaveValue(min === 8 ? 'Abc1!xy' : 'Abc1!xyzabc');
176
+ expect(screen.getByText('Too weak')).toBeInTheDocument();
177
+ expect(screen.getByText(`Use at least ${min} characters.`)).toBeInTheDocument();
178
+ await userEvent.click(screen.getByRole('button', { name: 'Create account' }));
179
+ expect(onSubmit).not.toHaveBeenCalled();
180
+ expect(password).toHaveAttribute('aria-invalid', 'true');
181
+ await expectNoAxeViolations(container);
182
+ await userEvent.type(password, 'z');
183
+ expect(password).toHaveValue(min === 8 ? 'Abc1!xyz' : 'Abc1!xyzabcz');
184
+ await waitFor(() => expect(screen.queryByText(`Use at least ${min} characters.`)).not.toBeInTheDocument());
185
+ await userEvent.click(screen.getByRole('button', { name: 'Create account' }));
186
+ expect(onSubmit).toHaveBeenCalledTimes(1);
187
+ },
188
+ );
189
+
190
+ it('replaces the form with an unavailable callout and consumer action', async () => {
191
+ const onAction = vi.fn();
192
+ const { container } = render(
193
+ <SignUpForm
194
+ unavailable={{
195
+ title: 'Registration unavailable',
196
+ message: 'Contact your administrator.',
197
+ action: <button onClick={onAction}>Request access</button>,
198
+ }}
199
+ />,
200
+ );
201
+ expect(screen.getByText('Registration unavailable')).toBeInTheDocument();
202
+ expect(screen.getByText('Contact your administrator.')).toBeInTheDocument();
203
+ expect(screen.queryByRole('form')).not.toBeInTheDocument();
204
+ expect(container.querySelector('input')).toBeNull();
205
+ expect(screen.queryByRole('button', { name: 'Create account' })).not.toBeInTheDocument();
206
+ await userEvent.click(screen.getByRole('button', { name: 'Request access' }));
207
+ expect(onAction).toHaveBeenCalledTimes(1);
208
+ await expectNoAxeViolations(container);
209
+ });
210
+
211
+ it('shows skeletons and cannot submit while policy is loading, then restores the form', async () => {
212
+ const onSubmit = vi.fn();
213
+ const { container, rerender } = render(<SignUpForm policyLoading onSubmit={onSubmit} />);
214
+ expect(screen.getByRole('status')).toHaveTextContent('Loading sign-up policy');
215
+ expect(container.querySelectorAll('[aria-hidden="true"]').length).toBeGreaterThan(0);
216
+ expect(screen.queryByRole('form')).not.toBeInTheDocument();
217
+ expect(container.querySelector('input')).toBeNull();
218
+ expect(screen.queryByRole('button', { name: 'Create account' })).not.toBeInTheDocument();
219
+ expect(onSubmit).not.toHaveBeenCalled();
220
+ await expectNoAxeViolations(container);
221
+ rerender(<SignUpForm policyLoading={false} onSubmit={onSubmit} />);
222
+ expect(screen.getByRole('form')).toBeInTheDocument();
223
+ });
224
+
225
+ it('validates non-empty optional email and uses consumer-supplied messages', async () => {
226
+ const onSubmit = vi.fn();
227
+ render(
228
+ <SignUpForm
229
+ fields={[
230
+ {
231
+ id: 'contact',
232
+ label: 'Contact',
233
+ type: 'email',
234
+ help: 'Optional contact address.',
235
+ messages: { invalid: 'Check the contact address.' },
236
+ },
237
+ ]}
238
+ terms={false}
239
+ messages={{ passwordTooShort: (minimum) => `Password needs ${minimum} characters.` }}
240
+ onSubmit={onSubmit}
241
+ sso={null}
242
+ />,
243
+ );
244
+ await userEvent.type(screen.getByLabelText('Contact'), 'invalid');
245
+ await userEvent.click(screen.getByRole('button', { name: 'Create account' }));
246
+ const summary = screen.getByRole('region');
247
+ expect(within(summary).getByRole('link', { name: 'Contact: Check the contact address.' })).toBeInTheDocument();
248
+ await userEvent.click(within(summary).getByRole('link', { name: 'Password: Password needs 8 characters.' }));
249
+ expect(screen.getByLabelText('Password')).toHaveFocus();
250
+ expect(onSubmit).not.toHaveBeenCalled();
251
+ });
252
+
253
+ it('keeps password help, strength and error descriptions linked and focuses consent from the summary', async () => {
254
+ const { container } = render(
255
+ <SignUpForm fields={[]} passwordHelp="Choose at least 8 characters to secure your account." sso={null} />,
256
+ );
257
+ const password = screen.getByLabelText('Password');
258
+ await userEvent.type(password, 'Abc1!xy');
259
+ await userEvent.click(screen.getByRole('button', { name: 'Create account' }));
260
+ expect(password).toHaveAccessibleDescription(
261
+ 'Strength: Too weak Choose at least 8 characters to secure your account. Use at least 8 characters.',
262
+ );
263
+ const summary = screen.getByRole('region');
264
+ await userEvent.click(within(summary).getByRole('link', { name: 'Password: Use at least 8 characters.' }));
265
+ expect(password).toHaveFocus();
266
+ await userEvent.click(screen.getByRole('button', { name: 'Show password' }));
267
+ expect(password).toHaveAttribute('type', 'text');
268
+ expect(password).toHaveAttribute('aria-invalid', 'true');
269
+ expect(password).toHaveAccessibleDescription(
270
+ 'Strength: Too weak Choose at least 8 characters to secure your account. Use at least 8 characters.',
271
+ );
272
+ const termsLink = within(summary).getByRole('link', { name: 'Terms: Accept the terms to continue.' });
273
+ const termsTarget = termsLink.getAttribute('href')!.slice(1);
274
+ expect(document.getElementById(termsTarget)).toBe(screen.getByRole('checkbox'));
275
+ expect(container.querySelectorAll(`[id="${termsTarget}"]`)).toHaveLength(1);
276
+ termsLink.focus();
277
+ await userEvent.keyboard('{Enter}');
278
+ expect(screen.getByRole('checkbox')).toHaveFocus();
279
+ expect(screen.getByRole('checkbox')).toHaveAccessibleDescription('Accept the terms to continue.');
280
+ await userEvent.keyboard(' ');
281
+ expect(screen.getByRole('checkbox')).toBeChecked();
282
+ expect(within(summary).queryByRole('link', { name: 'Terms: Accept the terms to continue.' })).not.toBeInTheDocument();
283
+ });
284
+
285
+ it('treats field ids as consumer keys, including names inherited by ordinary objects', async () => {
286
+ const onSubmit = vi.fn();
287
+ render(
288
+ <SignUpForm
289
+ fields={[{ id: 'constructor', label: 'Account identifier', required: true }]}
290
+ terms={false}
291
+ sso={null}
292
+ onSubmit={onSubmit}
293
+ />,
294
+ );
295
+ expect(screen.getByLabelText('Account identifier')).toHaveValue('');
296
+ await userEvent.type(screen.getByLabelText('Account identifier'), ' account ');
297
+ await userEvent.type(screen.getByLabelText('Password'), 'correct-horse-42');
298
+ await userEvent.click(screen.getByRole('button', { name: 'Create account' }));
299
+ expect(onSubmit).toHaveBeenCalledWith({
300
+ name: '',
301
+ email: '',
302
+ password: 'correct-horse-42',
303
+ fields: { constructor: 'account' },
304
+ });
305
+ });
306
+
307
+ it('preserves default submit-time validation and omits a validation summary in default mode', async () => {
308
+ render(<SignUpForm sso={null} />);
309
+ await userEvent.type(screen.getByLabelText('Password'), 'Abc1!xy');
310
+ expect(screen.getByText('Too weak')).toBeInTheDocument();
311
+ expect(screen.queryByText('Use at least 8 characters.')).not.toBeInTheDocument();
312
+ expect(screen.getByLabelText('Password')).not.toHaveAttribute('aria-invalid', 'true');
313
+ await userEvent.click(screen.getByRole('button', { name: 'Create account' }));
314
+ expect(screen.getByText('Use at least 8 characters.')).toBeInTheDocument();
315
+ expect(screen.queryByRole('region')).not.toBeInTheDocument();
316
+ });
@@ -0,0 +1,33 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import userEvent from '@testing-library/user-event';
3
+ import { expectNoAxeViolations } from '../../test/a11y';
4
+ import { SignUpForm } from './SignUpForm';
5
+
6
+ describe('SignUpForm', () => {
7
+ it('renders the default form', async () => {
8
+ const { container } = render(<SignUpForm />);
9
+ expect(screen.getByRole('form', { name: 'Create account' })).toBeInTheDocument();
10
+ await expectNoAxeViolations(container);
11
+ });
12
+
13
+ it('requires the terms and submits the values', async () => {
14
+ const onSubmit = vi.fn();
15
+ const { container } = render(<SignUpForm onSubmit={onSubmit} sso={null} />);
16
+ await userEvent.type(screen.getByLabelText('Full name'), 'Avery Collins');
17
+ await userEvent.type(screen.getByLabelText('Work email'), 'avery@example.com');
18
+ await userEvent.type(screen.getByLabelText('Password'), 'correct-horse-42');
19
+ expect(screen.getByText(/Strength:/)).toBeInTheDocument();
20
+ await userEvent.click(screen.getByRole('button', { name: 'Create account' }));
21
+ expect(onSubmit).not.toHaveBeenCalled();
22
+ const terms = screen.getByRole('checkbox', { name: /I agree/ });
23
+ expect(terms).toHaveAccessibleDescription('Accept the terms to continue.');
24
+ await expectNoAxeViolations(container);
25
+ await userEvent.click(terms);
26
+ await userEvent.click(screen.getByRole('button', { name: 'Create account' }));
27
+ expect(onSubmit).toHaveBeenCalledWith({
28
+ name: 'Avery Collins',
29
+ email: 'avery@example.com',
30
+ password: 'correct-horse-42',
31
+ });
32
+ });
33
+ });