@gntik-ai/blocks 0.0.0-stage → 0.5.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 (385) hide show
  1. package/dist/index.d.ts +3494 -0
  2. package/dist/index.js +14911 -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.test.tsx +33 -0
  49. package/src/auth/SignUpForm/SignUpForm.tsx +154 -0
  50. package/src/auth/SignUpForm/block.meta.ts +9 -0
  51. package/src/auth/SsoButtons/SsoButtons.test.tsx +23 -0
  52. package/src/auth/SsoButtons/SsoButtons.tsx +52 -0
  53. package/src/auth/SsoButtons/block.meta.ts +9 -0
  54. package/src/auth/SsoButtons/fixtures.ts +15 -0
  55. package/src/auth/index.ts +15 -0
  56. package/src/auth/shared.tsx +113 -0
  57. package/src/billing/CostBreakdown/CostBreakdown.test.tsx +24 -0
  58. package/src/billing/CostBreakdown/CostBreakdown.tsx +95 -0
  59. package/src/billing/CostBreakdown/block.meta.ts +10 -0
  60. package/src/billing/CostBreakdown/fixtures.ts +9 -0
  61. package/src/billing/InvoiceTable/InvoiceTable.test.tsx +29 -0
  62. package/src/billing/InvoiceTable/InvoiceTable.tsx +130 -0
  63. package/src/billing/InvoiceTable/block.meta.ts +10 -0
  64. package/src/billing/InvoiceTable/fixtures.ts +38 -0
  65. package/src/billing/PaymentMethodCard/PaymentMethodCard.test.tsx +28 -0
  66. package/src/billing/PaymentMethodCard/PaymentMethodCard.tsx +103 -0
  67. package/src/billing/PaymentMethodCard/block.meta.ts +10 -0
  68. package/src/billing/PaymentMethodCard/fixtures.ts +12 -0
  69. package/src/billing/PlanCard/PlanCard.baseline.test.tsx +26 -0
  70. package/src/billing/PlanCard/PlanCard.summary.test.tsx +227 -0
  71. package/src/billing/PlanCard/PlanCard.test.tsx +27 -0
  72. package/src/billing/PlanCard/PlanCard.tsx +140 -0
  73. package/src/billing/PlanCard/PlanSummary.tsx +101 -0
  74. package/src/billing/PlanCard/README.md +23 -0
  75. package/src/billing/PlanCard/__snapshots__/PlanCard.baseline.test.tsx.snap +5 -0
  76. package/src/billing/PlanCard/block.meta.ts +60 -0
  77. package/src/billing/PlanCard/examples/SummaryActionsOnly.tsx +12 -0
  78. package/src/billing/PlanCard/examples/SummaryFeaturesOnly.tsx +14 -0
  79. package/src/billing/PlanCard/examples/SummaryFull.tsx +28 -0
  80. package/src/billing/PlanCard/examples/SummaryMinimal.tsx +5 -0
  81. package/src/billing/PlanCard/examples/SummaryPriceOnly.tsx +15 -0
  82. package/src/billing/PlanCard/examples/SummaryRenewalOnly.tsx +14 -0
  83. package/src/billing/PlanCard/examples/SummaryUnassigned.tsx +5 -0
  84. package/src/billing/PlanCard/examples/SummaryUnassignedNoLabel.tsx +5 -0
  85. package/src/billing/PlanCard/examples/SummaryZeroPrice.tsx +15 -0
  86. package/src/billing/PlanCard/fixtures.ts +19 -0
  87. package/src/billing/PlanDefinitionCard/PlanDefinitionCard.test.tsx +111 -0
  88. package/src/billing/PlanDefinitionCard/PlanDefinitionCard.tsx +86 -0
  89. package/src/billing/PlanDefinitionCard/README.md +29 -0
  90. package/src/billing/PlanDefinitionCard/block.meta.ts +46 -0
  91. package/src/billing/PlanDefinitionCard/examples/Compact.tsx +23 -0
  92. package/src/billing/PlanDefinitionCard/examples/Full.tsx +23 -0
  93. package/src/billing/PlanDefinitionCard/fixtures.ts +12 -0
  94. package/src/billing/QuotaMeters/QuotaMeters.test.tsx +23 -0
  95. package/src/billing/QuotaMeters/QuotaMeters.tsx +92 -0
  96. package/src/billing/QuotaMeters/block.meta.ts +10 -0
  97. package/src/billing/QuotaMeters/fixtures.ts +9 -0
  98. package/src/billing/SpendVsBudget/SpendVsBudget.test.tsx +24 -0
  99. package/src/billing/SpendVsBudget/SpendVsBudget.tsx +105 -0
  100. package/src/billing/SpendVsBudget/block.meta.ts +10 -0
  101. package/src/billing/SpendVsBudget/fixtures.ts +18 -0
  102. package/src/billing/format.ts +42 -0
  103. package/src/billing/index.ts +21 -0
  104. package/src/builders/CodePanel/CodePanel.test.tsx +280 -0
  105. package/src/builders/CodePanel/CodePanel.tsx +239 -0
  106. package/src/builders/CodePanel/block.meta.ts +10 -0
  107. package/src/builders/CodePanel/examples/controlled.tsx +15 -0
  108. package/src/builders/CodePanel/examples/diagnostics.tsx +40 -0
  109. package/src/builders/CodePanel/fixtures.ts +95 -0
  110. package/src/builders/CodePanel/marker-store.ts +90 -0
  111. package/src/builders/CodePanel/markers.ts +62 -0
  112. package/src/builders/FlowBuilder/FlowBuilder.test.tsx +480 -0
  113. package/src/builders/FlowBuilder/FlowBuilder.tsx +343 -0
  114. package/src/builders/FlowBuilder/block.meta.ts +20 -0
  115. package/src/builders/FlowBuilder/connections.ts +67 -0
  116. package/src/builders/FlowBuilder/decorated-nodes.ts +80 -0
  117. package/src/builders/FlowBuilder/examples/extensions.tsx +120 -0
  118. package/src/builders/FlowBuilder/extensions.ts +37 -0
  119. package/src/builders/FlowBuilder/fixtures.ts +80 -0
  120. package/src/builders/FlowBuilder/parts.tsx +196 -0
  121. package/src/builders/FlowBuilder/problems.tsx +40 -0
  122. package/src/builders/LogViewer/LogViewer.test.tsx +97 -0
  123. package/src/builders/LogViewer/LogViewer.tsx +253 -0
  124. package/src/builders/LogViewer/block.meta.ts +10 -0
  125. package/src/builders/LogViewer/fixtures.ts +49 -0
  126. package/src/builders/index.ts +15 -0
  127. package/src/data-display/ActivityFeed/ActivityFeed.test.tsx +62 -0
  128. package/src/data-display/ActivityFeed/ActivityFeed.tsx +118 -0
  129. package/src/data-display/ActivityFeed/block.meta.ts +10 -0
  130. package/src/data-display/ActivityFeed/fixtures.ts +29 -0
  131. package/src/data-display/ChartCard/ChartCard.ranges.test.tsx +282 -0
  132. package/src/data-display/ChartCard/ChartCard.test.tsx +54 -0
  133. package/src/data-display/ChartCard/ChartCard.tsx +218 -0
  134. package/src/data-display/ChartCard/block.meta.ts +10 -0
  135. package/src/data-display/ChartCard/examples/custom-ranges.tsx +37 -0
  136. package/src/data-display/ChartCard/examples/heatmap.tsx +20 -0
  137. package/src/data-display/ChartCard/examples/toolbar-owned-range.tsx +34 -0
  138. package/src/data-display/ChartCard/fixtures.ts +82 -0
  139. package/src/data-display/CommentThread/CommentThread.test.tsx +29 -0
  140. package/src/data-display/CommentThread/CommentThread.tsx +120 -0
  141. package/src/data-display/CommentThread/block.meta.ts +10 -0
  142. package/src/data-display/CommentThread/fixtures.ts +28 -0
  143. package/src/data-display/DescriptionListCard/DescriptionListCard.test.tsx +28 -0
  144. package/src/data-display/DescriptionListCard/DescriptionListCard.tsx +120 -0
  145. package/src/data-display/DescriptionListCard/block.meta.ts +9 -0
  146. package/src/data-display/DescriptionListCard/fixtures.ts +12 -0
  147. package/src/data-display/KpiRow/KpiRow.test.tsx +29 -0
  148. package/src/data-display/KpiRow/KpiRow.tsx +101 -0
  149. package/src/data-display/KpiRow/block.meta.ts +10 -0
  150. package/src/data-display/KpiRow/fixtures.ts +34 -0
  151. package/src/data-display/ResourceCardGrid/ResourceCardGrid.test.tsx +22 -0
  152. package/src/data-display/ResourceCardGrid/ResourceCardGrid.tsx +98 -0
  153. package/src/data-display/ResourceCardGrid/block.meta.ts +10 -0
  154. package/src/data-display/ResourceCardGrid/fixtures.ts +72 -0
  155. package/src/data-display/StatCard/StatCard.test.tsx +30 -0
  156. package/src/data-display/StatCard/StatCard.tsx +138 -0
  157. package/src/data-display/StatCard/block.meta.ts +10 -0
  158. package/src/data-display/StatCard/fixtures.ts +26 -0
  159. package/src/data-display/StatusTimeline/StatusTimeline.test.tsx +153 -0
  160. package/src/data-display/StatusTimeline/StatusTimeline.tsx +114 -0
  161. package/src/data-display/StatusTimeline/block.meta.ts +10 -0
  162. package/src/data-display/StatusTimeline/examples/Empty.tsx +6 -0
  163. package/src/data-display/StatusTimeline/fixtures.ts +62 -0
  164. package/src/data-display/index.ts +34 -0
  165. package/src/data-display/shared/TrendDelta.tsx +60 -0
  166. package/src/feedback/ConfirmDestructive/ConfirmDestructive.test.tsx +47 -0
  167. package/src/feedback/ConfirmDestructive/ConfirmDestructive.tsx +145 -0
  168. package/src/feedback/ConfirmDestructive/block.meta.ts +10 -0
  169. package/src/feedback/EmptyStates/EmptyStates.test.tsx +48 -0
  170. package/src/feedback/EmptyStates/EmptyStates.tsx +191 -0
  171. package/src/feedback/EmptyStates/block.meta.ts +10 -0
  172. package/src/feedback/EmptyStates/fixtures.ts +6 -0
  173. package/src/feedback/ErrorPanel/ErrorPanel.test.tsx +37 -0
  174. package/src/feedback/ErrorPanel/ErrorPanel.tsx +146 -0
  175. package/src/feedback/ErrorPanel/block.meta.ts +10 -0
  176. package/src/feedback/InlineCallout/InlineCallout.test.tsx +33 -0
  177. package/src/feedback/InlineCallout/InlineCallout.tsx +81 -0
  178. package/src/feedback/InlineCallout/block.meta.ts +9 -0
  179. package/src/feedback/Skeletons/Skeletons.test.tsx +25 -0
  180. package/src/feedback/Skeletons/Skeletons.tsx +142 -0
  181. package/src/feedback/Skeletons/block.meta.ts +10 -0
  182. package/src/feedback/SystemBanner/SystemBanner.test.tsx +29 -0
  183. package/src/feedback/SystemBanner/SystemBanner.tsx +105 -0
  184. package/src/feedback/SystemBanner/block.meta.ts +9 -0
  185. package/src/feedback/index.ts +14 -0
  186. package/src/feedback/types.ts +13 -0
  187. package/src/forms/CreateApiKeyDialog/CreateApiKeyDialog.test.tsx +36 -0
  188. package/src/forms/CreateApiKeyDialog/CreateApiKeyDialog.tsx +197 -0
  189. package/src/forms/CreateApiKeyDialog/block.meta.ts +10 -0
  190. package/src/forms/CreateApiKeyDialog/fixtures.ts +31 -0
  191. package/src/forms/CredentialDisclosureDialog/CredentialDisclosureDialog.test.tsx +339 -0
  192. package/src/forms/CredentialDisclosureDialog/CredentialDisclosureDialog.tsx +193 -0
  193. package/src/forms/CredentialDisclosureDialog/block.meta.ts +10 -0
  194. package/src/forms/CredentialDisclosureDialog/examples/Fresh.tsx +32 -0
  195. package/src/forms/CredentialDisclosureDialog/examples/ResponseGating.tsx +69 -0
  196. package/src/forms/CredentialDisclosureDialog/examples/Stored.tsx +32 -0
  197. package/src/forms/CredentialDisclosureDialog/fixtures.ts +14 -0
  198. package/src/forms/DangerZone/ConfirmDestructiveDialog.tsx +101 -0
  199. package/src/forms/DangerZone/DangerZone.test.tsx +28 -0
  200. package/src/forms/DangerZone/DangerZone.tsx +86 -0
  201. package/src/forms/DangerZone/block.meta.ts +10 -0
  202. package/src/forms/DangerZone/fixtures.ts +36 -0
  203. package/src/forms/DateRangeFilter/DateRangeFilter.test.tsx +26 -0
  204. package/src/forms/DateRangeFilter/DateRangeFilter.tsx +83 -0
  205. package/src/forms/DateRangeFilter/block.meta.ts +9 -0
  206. package/src/forms/DialogWizard/DialogWizard.test.tsx +95 -0
  207. package/src/forms/DialogWizard/DialogWizard.tsx +39 -0
  208. package/src/forms/DialogWizard/block.meta.ts +10 -0
  209. package/src/forms/DialogWizard/examples/CreateResource.tsx +65 -0
  210. package/src/forms/DialogWizard/fixtures.ts +20 -0
  211. package/src/forms/FileUploadPanel/FileUploadPanel.test.tsx +43 -0
  212. package/src/forms/FileUploadPanel/FileUploadPanel.tsx +244 -0
  213. package/src/forms/FileUploadPanel/block.meta.ts +10 -0
  214. package/src/forms/FileUploadPanel/fixtures.ts +66 -0
  215. package/src/forms/FormSection/FormSection.test.tsx +34 -0
  216. package/src/forms/FormSection/FormSection.tsx +77 -0
  217. package/src/forms/FormSection/block.meta.ts +9 -0
  218. package/src/forms/InviteMembersDialog/InviteMembersDialog.test.tsx +37 -0
  219. package/src/forms/InviteMembersDialog/InviteMembersDialog.tsx +183 -0
  220. package/src/forms/InviteMembersDialog/block.meta.ts +10 -0
  221. package/src/forms/InviteMembersDialog/fixtures.ts +14 -0
  222. package/src/forms/SettingsRow/SettingsRow.test.tsx +50 -0
  223. package/src/forms/SettingsRow/SettingsRow.tsx +74 -0
  224. package/src/forms/SettingsRow/block.meta.ts +9 -0
  225. package/src/forms/ValidationSummary/ValidationSummary.test.tsx +46 -0
  226. package/src/forms/ValidationSummary/ValidationSummary.tsx +75 -0
  227. package/src/forms/ValidationSummary/block.meta.ts +9 -0
  228. package/src/forms/ValidationSummary/fixtures.ts +13 -0
  229. package/src/forms/index.ts +27 -0
  230. package/src/index.ts +14 -0
  231. package/src/marketing/ComparisonTable/ComparisonTable.test.tsx +69 -0
  232. package/src/marketing/ComparisonTable/ComparisonTable.tsx +245 -0
  233. package/src/marketing/ComparisonTable/block.meta.ts +10 -0
  234. package/src/marketing/ComparisonTable/examples/options.tsx +48 -0
  235. package/src/marketing/ComparisonTable/fixtures.ts +83 -0
  236. package/src/marketing/Faq/Faq.test.tsx +25 -0
  237. package/src/marketing/Faq/Faq.tsx +68 -0
  238. package/src/marketing/Faq/block.meta.ts +10 -0
  239. package/src/marketing/Faq/fixtures.ts +32 -0
  240. package/src/marketing/FeatureGrid/FeatureGrid.test.tsx +19 -0
  241. package/src/marketing/FeatureGrid/FeatureGrid.tsx +79 -0
  242. package/src/marketing/FeatureGrid/block.meta.ts +10 -0
  243. package/src/marketing/FeatureGrid/fixtures.ts +36 -0
  244. package/src/marketing/Hero/Hero.test.tsx +33 -0
  245. package/src/marketing/Hero/Hero.tsx +107 -0
  246. package/src/marketing/Hero/block.meta.ts +10 -0
  247. package/src/marketing/PricingTable/PricingTable.test.tsx +92 -0
  248. package/src/marketing/PricingTable/PricingTable.tsx +323 -0
  249. package/src/marketing/PricingTable/block.meta.ts +10 -0
  250. package/src/marketing/PricingTable/fixtures.ts +34 -0
  251. package/src/marketing/actions.tsx +40 -0
  252. package/src/marketing/index.ts +15 -0
  253. package/src/meta.ts +23 -0
  254. package/src/page-chrome/BulkActionBar/BulkActionBar.test.tsx +34 -0
  255. package/src/page-chrome/BulkActionBar/BulkActionBar.tsx +76 -0
  256. package/src/page-chrome/BulkActionBar/block.meta.ts +9 -0
  257. package/src/page-chrome/PageHeader/PageHeader.test.tsx +68 -0
  258. package/src/page-chrome/PageHeader/PageHeader.tsx +152 -0
  259. package/src/page-chrome/PageHeader/block.meta.ts +10 -0
  260. package/src/page-chrome/PageHeader/fixtures.ts +29 -0
  261. package/src/page-chrome/PageToolbar/PageToolbar.test.tsx +38 -0
  262. package/src/page-chrome/PageToolbar/PageToolbar.tsx +138 -0
  263. package/src/page-chrome/PageToolbar/block.meta.ts +10 -0
  264. package/src/page-chrome/PageToolbar/fixtures.ts +8 -0
  265. package/src/page-chrome/SectionHeader/SectionHeader.test.tsx +28 -0
  266. package/src/page-chrome/SectionHeader/SectionHeader.tsx +73 -0
  267. package/src/page-chrome/SectionHeader/block.meta.ts +9 -0
  268. package/src/page-chrome/StickyActionBar/StickyActionBar.test.tsx +29 -0
  269. package/src/page-chrome/StickyActionBar/StickyActionBar.tsx +66 -0
  270. package/src/page-chrome/StickyActionBar/block.meta.ts +9 -0
  271. package/src/page-chrome/index.ts +13 -0
  272. package/src/page-chrome/types.ts +25 -0
  273. package/src/scheduling/AgendaList/AgendaList.test.tsx +28 -0
  274. package/src/scheduling/AgendaList/AgendaList.tsx +165 -0
  275. package/src/scheduling/AgendaList/block.meta.ts +10 -0
  276. package/src/scheduling/MonthCalendar/MonthCalendar.test.tsx +32 -0
  277. package/src/scheduling/MonthCalendar/MonthCalendar.tsx +223 -0
  278. package/src/scheduling/MonthCalendar/block.meta.ts +10 -0
  279. package/src/scheduling/TimeSlotPicker/TimeSlotPicker.test.tsx +26 -0
  280. package/src/scheduling/TimeSlotPicker/TimeSlotPicker.tsx +115 -0
  281. package/src/scheduling/TimeSlotPicker/block.meta.ts +10 -0
  282. package/src/scheduling/TimeSlotPicker/fixtures.ts +10 -0
  283. package/src/scheduling/WeekCalendar/WeekCalendar.test.tsx +27 -0
  284. package/src/scheduling/WeekCalendar/WeekCalendar.tsx +274 -0
  285. package/src/scheduling/WeekCalendar/block.meta.ts +10 -0
  286. package/src/scheduling/index.ts +11 -0
  287. package/src/scheduling/shared/CalendarHeader.tsx +54 -0
  288. package/src/scheduling/shared/EventTile.tsx +21 -0
  289. package/src/scheduling/shared/dates.ts +98 -0
  290. package/src/scheduling/shared/events.ts +90 -0
  291. package/src/scheduling/shared/fixtures.ts +54 -0
  292. package/src/shell/AppSidebar/AppSidebar.test.tsx +38 -0
  293. package/src/shell/AppSidebar/AppSidebar.tsx +226 -0
  294. package/src/shell/AppSidebar/block.meta.ts +10 -0
  295. package/src/shell/AppSidebar/fixtures.ts +58 -0
  296. package/src/shell/AppTopbar/AppTopbar.test.tsx +41 -0
  297. package/src/shell/AppTopbar/AppTopbar.tsx +82 -0
  298. package/src/shell/AppTopbar/block.meta.ts +19 -0
  299. package/src/shell/AppTopbar/fixtures.ts +22 -0
  300. package/src/shell/EnvironmentBadge/EnvironmentBadge.test.tsx +22 -0
  301. package/src/shell/EnvironmentBadge/EnvironmentBadge.tsx +55 -0
  302. package/src/shell/EnvironmentBadge/block.meta.ts +10 -0
  303. package/src/shell/GlobalSearch/GlobalSearch.test.tsx +28 -0
  304. package/src/shell/GlobalSearch/GlobalSearch.tsx +80 -0
  305. package/src/shell/GlobalSearch/block.meta.ts +10 -0
  306. package/src/shell/GlobalSearch/fixtures.ts +64 -0
  307. package/src/shell/index.ts +11 -0
  308. package/src/styles.css +2 -0
  309. package/src/tables/DataTable/DataTable.edit-views.test.tsx +249 -0
  310. package/src/tables/DataTable/DataTable.layout.test.tsx +188 -0
  311. package/src/tables/DataTable/DataTable.single-select.test.tsx +501 -0
  312. package/src/tables/DataTable/DataTable.test.tsx +268 -0
  313. package/src/tables/DataTable/DataTable.tsx +520 -0
  314. package/src/tables/DataTable/block.meta.ts +27 -0
  315. package/src/tables/DataTable/column-menu.tsx +109 -0
  316. package/src/tables/DataTable/column-sizing.ts +166 -0
  317. package/src/tables/DataTable/data-table-body.tsx +194 -0
  318. package/src/tables/DataTable/data-table-props.ts +156 -0
  319. package/src/tables/DataTable/data-table-row.tsx +220 -0
  320. package/src/tables/DataTable/data-table-utils.ts +195 -0
  321. package/src/tables/DataTable/density-toggle.tsx +30 -0
  322. package/src/tables/DataTable/editable-cell.tsx +274 -0
  323. package/src/tables/DataTable/examples/editable.tsx +71 -0
  324. package/src/tables/DataTable/examples/grouped.tsx +18 -0
  325. package/src/tables/DataTable/examples/resizable-pinned.tsx +20 -0
  326. package/src/tables/DataTable/examples/saved-views.tsx +61 -0
  327. package/src/tables/DataTable/examples/single-selection.tsx +55 -0
  328. package/src/tables/DataTable/examples/virtualized.tsx +20 -0
  329. package/src/tables/DataTable/fixtures.ts +119 -0
  330. package/src/tables/DataTable/header-cell.tsx +86 -0
  331. package/src/tables/DataTable/resize-handle.tsx +88 -0
  332. package/src/tables/DataTable/single-selection.tsx +149 -0
  333. package/src/tables/DataTable/use-table-views.ts +79 -0
  334. package/src/tables/DataTable/use-virtual-rows.ts +83 -0
  335. package/src/tables/DataTable/views.tsx +186 -0
  336. package/src/tables/FilterBar/FilterBar.test.tsx +43 -0
  337. package/src/tables/FilterBar/FilterBar.tsx +199 -0
  338. package/src/tables/FilterBar/block.meta.ts +10 -0
  339. package/src/tables/FilterBar/fixtures.ts +32 -0
  340. package/src/tables/InlineEditRow/InlineEditRow.test.tsx +41 -0
  341. package/src/tables/InlineEditRow/InlineEditRow.tsx +215 -0
  342. package/src/tables/InlineEditRow/block.meta.ts +10 -0
  343. package/src/tables/InlineEditRow/fixtures.ts +28 -0
  344. package/src/tables/PaginationFooter/PaginationFooter.test.tsx +29 -0
  345. package/src/tables/PaginationFooter/PaginationFooter.tsx +94 -0
  346. package/src/tables/PaginationFooter/block.meta.ts +10 -0
  347. package/src/tables/SubordinateList/SubordinateList.test.tsx +351 -0
  348. package/src/tables/SubordinateList/SubordinateList.tsx +140 -0
  349. package/src/tables/SubordinateList/block.meta.ts +10 -0
  350. package/src/tables/SubordinateList/examples/ResourceForm.tsx +129 -0
  351. package/src/tables/SubordinateList/examples/empty.tsx +5 -0
  352. package/src/tables/SubordinateList/examples/error.tsx +5 -0
  353. package/src/tables/SubordinateList/examples/loading.tsx +5 -0
  354. package/src/tables/TruncatedCollectionFooter/TruncatedCollectionFooter.test.tsx +159 -0
  355. package/src/tables/TruncatedCollectionFooter/TruncatedCollectionFooter.tsx +60 -0
  356. package/src/tables/TruncatedCollectionFooter/block.meta.ts +10 -0
  357. package/src/tables/TruncatedCollectionFooter/examples/exact.tsx +6 -0
  358. package/src/tables/TruncatedCollectionFooter/examples/has-more.tsx +12 -0
  359. package/src/tables/TruncatedCollectionFooter/fixtures.ts +2 -0
  360. package/src/tables/index.ts +45 -0
  361. package/src/team/MembersTable/MembersTable.test.tsx +29 -0
  362. package/src/team/MembersTable/MembersTable.tsx +141 -0
  363. package/src/team/MembersTable/block.meta.ts +9 -0
  364. package/src/team/MembersTable/fixtures.ts +19 -0
  365. package/src/team/NotificationMatrix/NotificationMatrix.test.tsx +36 -0
  366. package/src/team/NotificationMatrix/NotificationMatrix.tsx +144 -0
  367. package/src/team/NotificationMatrix/block.meta.ts +9 -0
  368. package/src/team/NotificationMatrix/fixtures.ts +43 -0
  369. package/src/team/PendingInvitations/PendingInvitations.test.tsx +30 -0
  370. package/src/team/PendingInvitations/PendingInvitations.tsx +132 -0
  371. package/src/team/PendingInvitations/block.meta.ts +9 -0
  372. package/src/team/PendingInvitations/fixtures.ts +39 -0
  373. package/src/team/ProfileHeader/ProfileHeader.test.tsx +29 -0
  374. package/src/team/ProfileHeader/ProfileHeader.tsx +106 -0
  375. package/src/team/ProfileHeader/block.meta.ts +10 -0
  376. package/src/team/ProfileHeader/fixtures.ts +28 -0
  377. package/src/team/SessionsDevices/SessionsDevices.test.tsx +30 -0
  378. package/src/team/SessionsDevices/SessionsDevices.tsx +126 -0
  379. package/src/team/SessionsDevices/block.meta.ts +10 -0
  380. package/src/team/SessionsDevices/fixtures.ts +58 -0
  381. package/src/team/index.ts +15 -0
  382. package/src/test/a11y.ts +18 -0
  383. package/src/test/heading-levels.test.tsx +69 -0
  384. package/src/test/setup.ts +36 -0
  385. package/README.md +0 -3
@@ -0,0 +1,501 @@
1
+ import { render, screen, within } from '@testing-library/react';
2
+ import userEvent from '@testing-library/user-event';
3
+ import { I18nProvider } from '@gntik-ai/ui';
4
+ import { expectNoAxeViolations } from '../../test/a11y';
5
+ import { DataTable, type DataTableProps } from './DataTable';
6
+ import SingleSelectionExample from './examples/single-selection';
7
+
8
+ const rows = [
9
+ { id: 'a', name: 'Alpha', disabled: false },
10
+ { id: 'b', name: 'Beta', disabled: true },
11
+ { id: 'c', name: 'Gamma', disabled: false },
12
+ ];
13
+ const columns = [{ id: 'name', header: 'Name', accessor: (row: (typeof rows)[number]) => row.name }];
14
+ const base = { rows, columns, showColumnMenu: false, showDensityToggle: false };
15
+
16
+ // @ts-expect-error Group labels belong to single selection, not checkbox selection.
17
+ const multipleLegend: DataTableProps<(typeof rows)[number]> = { selectionMode: 'multiple', legend: 'Choices' };
18
+ // @ts-expect-error An ungrouped table cannot silently discard a radio-group label.
19
+ const noneLabel: DataTableProps<(typeof rows)[number]> = { selectionMode: 'none', 'aria-label': 'Choices' };
20
+ // @ts-expect-error The default checkbox mode does not provide a labelled radio group.
21
+ const defaultLabelledBy: DataTableProps<(typeof rows)[number]> = { 'aria-labelledby': 'choices-label' };
22
+ void multipleLegend;
23
+ void noneLabel;
24
+ void defaultLabelledBy;
25
+
26
+ describe('DataTable single selection', () => {
27
+ it('provides a non-paginated list and a versions example with a checked disabled version', async () => {
28
+ const { container } = render(<SingleSelectionExample />);
29
+ expect(screen.getByRole('group', { name: 'Available items' })).toBeInTheDocument();
30
+ const versions = within(screen.getByRole('group', { name: 'Available versions' }));
31
+ expect(versions.getByRole('radio', { checked: true })).toBeDisabled();
32
+ expect(screen.queryByRole('navigation')).toBeNull();
33
+ await expectNoAxeViolations(container);
34
+ });
35
+ it('renders native radios and changes exactly one choice without checkbox or bulk selection controls', async () => {
36
+ const user = userEvent.setup();
37
+ const onSelectedIdChange = vi.fn();
38
+ render(
39
+ <DataTable
40
+ {...base}
41
+ selectionMode="single"
42
+ paginated={false}
43
+ legend="Available items"
44
+ selectionName="items"
45
+ defaultSelectedId="a"
46
+ onSelectedIdChange={onSelectedIdChange}
47
+ selectedIds={['a']}
48
+ bulkActions={() => <button>Bulk action</button>}
49
+ />,
50
+ );
51
+ const group = screen.getByRole('group', { name: 'Available items' });
52
+ const radios = within(group).getAllByRole('radio');
53
+ expect(radios).toHaveLength(3);
54
+ for (const radio of radios) {
55
+ expect(radio.tagName).toBe('INPUT');
56
+ expect(radio).toHaveAttribute('type', 'radio');
57
+ expect(radio).toHaveAttribute('name', 'items');
58
+ }
59
+ expect(screen.queryByRole('checkbox')).toBeNull();
60
+ expect(screen.queryByText(/selected/)).toBeNull();
61
+ expect(screen.queryByRole('button', { name: 'Bulk action' })).toBeNull();
62
+ await user.click(screen.getByRole('radio', { name: 'Gamma' }));
63
+ expect(screen.getByRole('radio', { name: 'Alpha' })).not.toBeChecked();
64
+ expect(screen.getByRole('radio', { name: 'Gamma' })).toBeChecked();
65
+ expect(screen.getByRole('radio', { name: 'Gamma' }).closest('tr')).toHaveAttribute('data-selected');
66
+ expect(onSelectedIdChange.mock.calls).toEqual([['c']]);
67
+ });
68
+
69
+ it('uses one tab stop, skips disabled choices, wraps arrows and leaves the group with Tab', async () => {
70
+ const user = userEvent.setup();
71
+ const change = vi.fn();
72
+ render(
73
+ <>
74
+ <button>Before</button>
75
+ <DataTable
76
+ {...base}
77
+ selectionMode="single"
78
+ legend="Choices"
79
+ defaultSelectedId="a"
80
+ isRowDisabled={(row) => row.disabled}
81
+ onSelectedIdChange={change}
82
+ />
83
+ <button>After</button>
84
+ </>,
85
+ );
86
+ const alpha = screen.getByRole('radio', { name: 'Alpha' });
87
+ const beta = screen.getByRole('radio', { name: 'Beta' });
88
+ const gamma = screen.getByRole('radio', { name: 'Gamma' });
89
+ expect(beta).toBeDisabled();
90
+ await user.click(beta);
91
+ expect(change).not.toHaveBeenCalled();
92
+ await user.tab();
93
+ expect(screen.getByRole('button', { name: 'Before' })).toHaveFocus();
94
+ await user.tab();
95
+ expect(alpha).toHaveFocus();
96
+ await user.keyboard('{ArrowDown}');
97
+ expect(gamma).toHaveFocus();
98
+ expect(gamma).toBeChecked();
99
+ await user.keyboard('{ArrowRight}');
100
+ expect(alpha).toHaveFocus();
101
+ await user.keyboard('{ArrowUp}');
102
+ expect(gamma).toHaveFocus();
103
+ await user.keyboard('{ArrowLeft}');
104
+ expect(alpha).toHaveFocus();
105
+ expect(change.mock.calls).toEqual([['c'], ['a'], ['c'], ['a']]);
106
+ await user.tab({ shift: true });
107
+ expect(screen.getByRole('button', { name: 'Before' })).toHaveFocus();
108
+ await user.tab();
109
+ await user.tab();
110
+ expect(screen.getByRole('button', { name: 'After' })).toHaveFocus();
111
+ });
112
+
113
+ it('enters on a checked enabled radio after the first row', async () => {
114
+ const user = userEvent.setup();
115
+ render(
116
+ <>
117
+ <button>Before</button>
118
+ <DataTable {...base} selectionMode="single" legend="Choices" defaultSelectedId="c" />
119
+ <button>After</button>
120
+ </>,
121
+ );
122
+ await user.tab();
123
+ await user.tab();
124
+ expect(screen.getByRole('radio', { name: 'Gamma' })).toHaveFocus();
125
+ await user.tab({ shift: true });
126
+ expect(screen.getByRole('button', { name: 'Before' })).toHaveFocus();
127
+ });
128
+
129
+ it('translates the selection column while preserving the consumer group label', () => {
130
+ render(
131
+ <I18nProvider locale="es">
132
+ <DataTable {...base} selectionMode="single" legend="Choices" />
133
+ </I18nProvider>,
134
+ );
135
+ expect(screen.getByRole('columnheader', { name: 'Selección' })).toBeInTheDocument();
136
+ expect(screen.getByRole('group', { name: 'Choices' })).toBeInTheDocument();
137
+ });
138
+
139
+ it('warns about duplicate supplied names and missing group labels in development', () => {
140
+ vi.stubEnv('NODE_ENV', 'development');
141
+ const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
142
+ try {
143
+ const { unmount } = render(
144
+ <>
145
+ <DataTable {...base} selectionMode="single" selectionName="duplicate-items" legend="First" />
146
+ <DataTable {...base} selectionMode="single" selectionName="duplicate-items" legend="Second" />
147
+ <DataTable {...base} selectionMode="single" />
148
+ </>,
149
+ );
150
+ expect(warn).toHaveBeenCalledWith(expect.stringContaining('duplicate-items'));
151
+ expect(warn).toHaveBeenCalledWith(expect.stringContaining('legend, aria-label or aria-labelledby'));
152
+ unmount();
153
+ warn.mockClear();
154
+ render(<DataTable {...base} selectionMode="single" selectionName="duplicate-items" legend="New" />);
155
+ expect(warn).not.toHaveBeenCalled();
156
+ } finally {
157
+ warn.mockRestore();
158
+ vi.unstubAllEnvs();
159
+ }
160
+ });
161
+
162
+ it('accepts a numeric zero legend as the consumer group label without warning', () => {
163
+ vi.stubEnv('NODE_ENV', 'development');
164
+ const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
165
+ try {
166
+ render(<DataTable {...base} selectionMode="single" legend={0} />);
167
+ expect(screen.getByRole('group', { name: '0' })).toBeInTheDocument();
168
+ expect(warn).not.toHaveBeenCalled();
169
+ } finally {
170
+ warn.mockRestore();
171
+ vi.unstubAllEnvs();
172
+ }
173
+ });
174
+
175
+ it('rejects pagination in types and warns while rendering all rows if the type check is bypassed', () => {
176
+ // @ts-expect-error Single selection cannot be paginated.
177
+ const invalid: DataTableProps<(typeof rows)[number]> = {
178
+ ...base,
179
+ selectionMode: 'single',
180
+ paginated: true,
181
+ legend: 'Items',
182
+ };
183
+ vi.stubEnv('NODE_ENV', 'development');
184
+ const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
185
+ try {
186
+ render(<DataTable {...invalid} defaultPageSize={1} virtualize={true} />);
187
+ expect(warn).toHaveBeenCalledWith(expect.stringContaining('paginated'));
188
+ expect(screen.getAllByRole('radio')).toHaveLength(3);
189
+ expect(screen.queryByRole('navigation')).toBeNull();
190
+ expect(screen.getByRole('table')).not.toHaveAttribute('data-virtualized');
191
+ } finally {
192
+ warn.mockRestore();
193
+ vi.unstubAllEnvs();
194
+ }
195
+ });
196
+
197
+ it('isolates generated radio names between tables', async () => {
198
+ const user = userEvent.setup();
199
+ render(
200
+ <>
201
+ <DataTable {...base} selectionMode="single" legend="First" defaultSelectedId="a" />
202
+ <DataTable {...base} selectionMode="single" legend="Second" defaultSelectedId="c" />
203
+ </>,
204
+ );
205
+ const first = within(screen.getByRole('group', { name: 'First' }));
206
+ const second = within(screen.getByRole('group', { name: 'Second' }));
207
+ const firstName = first.getByRole('radio', { name: 'Alpha' }).getAttribute('name');
208
+ const secondName = second.getByRole('radio', { name: 'Alpha' }).getAttribute('name');
209
+ expect(firstName).toBeTruthy();
210
+ expect(secondName).toBeTruthy();
211
+ expect(firstName).not.toBe(secondName);
212
+ for (const radio of first.getAllByRole('radio')) expect(radio).toHaveAttribute('name', firstName!);
213
+ for (const radio of second.getAllByRole('radio')) expect(radio).toHaveAttribute('name', secondName!);
214
+ await user.click(first.getByRole('radio', { name: 'Gamma' }));
215
+ await user.click(second.getByRole('radio', { name: 'Beta' }));
216
+ expect(first.getByRole('radio', { name: 'Gamma' })).toBeChecked();
217
+ expect(second.getByRole('radio', { name: 'Beta' })).toBeChecked();
218
+ });
219
+
220
+ it('uses consumer aria labels and row labels without overriding table roles', () => {
221
+ const { rerender } = render(
222
+ <DataTable
223
+ {...base}
224
+ selectionMode="single"
225
+ aria-label="Choose an item"
226
+ getRowLabel={(row) => `Item ${row.id}`}
227
+ />,
228
+ );
229
+ expect(screen.getByRole('group', { name: 'Choose an item' })).toBeInTheDocument();
230
+ expect(screen.getByRole('radio', { name: 'Item a' })).toBeInTheDocument();
231
+ rerender(
232
+ <>
233
+ <span id="choices-label">Named choices</span>
234
+ <DataTable {...base} selectionMode="single" aria-labelledby="choices-label" />
235
+ </>,
236
+ );
237
+ const group = screen.getByRole('group', { name: 'Named choices' });
238
+ expect(within(group).getByRole('table', { name: 'Deployments' })).not.toHaveAttribute('role');
239
+ expect(within(group).getAllByRole('row')).toHaveLength(4);
240
+ expect(within(group).getAllByRole('columnheader')).toHaveLength(2);
241
+ expect(within(group).getAllByRole('cell')).toHaveLength(6);
242
+ expect(group.querySelector('table [role]')).toBeNull();
243
+ });
244
+
245
+ it('selects with Space and supports controlled values including clearing', async () => {
246
+ const user = userEvent.setup();
247
+ const change = vi.fn();
248
+ const { rerender } = render(
249
+ <DataTable {...base} selectionMode="single" aria-label="Choices" selectedId={null} onSelectedIdChange={change} />,
250
+ );
251
+ await user.tab();
252
+ expect(screen.getByRole('radio', { name: 'Alpha' })).toHaveFocus();
253
+ await user.keyboard(' ');
254
+ expect(change.mock.calls).toEqual([['a']]);
255
+ expect(screen.getByRole('radio', { name: 'Alpha' })).not.toBeChecked();
256
+ rerender(
257
+ <DataTable {...base} selectionMode="single" aria-label="Choices" selectedId="a" onSelectedIdChange={change} />,
258
+ );
259
+ expect(screen.getByRole('radio', { name: 'Alpha' })).toBeChecked();
260
+ await user.click(screen.getByRole('radio', { name: 'Gamma' }));
261
+ expect(change.mock.calls).toEqual([['a'], ['c']]);
262
+ expect(screen.getByRole('radio', { name: 'Alpha' })).toBeChecked();
263
+ rerender(
264
+ <DataTable {...base} selectionMode="single" aria-label="Choices" selectedId="c" onSelectedIdChange={change} />,
265
+ );
266
+ expect(screen.getByRole('radio', { name: 'Gamma' })).toBeChecked();
267
+ rerender(
268
+ <DataTable {...base} selectionMode="single" aria-label="Choices" selectedId={null} onSelectedIdChange={change} />,
269
+ );
270
+ expect(screen.queryByRole('radio', { checked: true })).toBeNull();
271
+ expect(change).toHaveBeenCalledTimes(2);
272
+ });
273
+
274
+ it('keeps a disabled selected id and enters on the first enabled row without an automatic callback', async () => {
275
+ const user = userEvent.setup();
276
+ const change = vi.fn();
277
+ const { rerender } = render(
278
+ <DataTable
279
+ {...base}
280
+ selectionMode="single"
281
+ legend="Choices"
282
+ defaultSelectedId="a"
283
+ isRowDisabled={(row) => row.disabled}
284
+ onSelectedIdChange={change}
285
+ />,
286
+ );
287
+ rerender(
288
+ <DataTable
289
+ {...base}
290
+ rows={rows.map((row) => ({ ...row, disabled: row.id !== 'c' }))}
291
+ selectionMode="single"
292
+ legend="Choices"
293
+ defaultSelectedId="a"
294
+ isRowDisabled={(row) => row.disabled}
295
+ onSelectedIdChange={change}
296
+ />,
297
+ );
298
+ const alpha = screen.getByRole('radio', { name: 'Alpha' });
299
+ expect(alpha).toBeChecked();
300
+ expect(alpha).toBeDisabled();
301
+ expect(alpha.closest('tr')).toHaveAttribute('data-selected');
302
+ await user.click(alpha);
303
+ await user.tab();
304
+ expect(screen.getByRole('radio', { name: 'Gamma' })).toHaveFocus();
305
+ expect(change).not.toHaveBeenCalled();
306
+ await user.keyboard(' ');
307
+ expect(screen.getByRole('radio', { name: 'Gamma' })).toBeChecked();
308
+ expect(change.mock.calls).toEqual([['c']]);
309
+ });
310
+
311
+ it('enters on the first enabled radio without selecting, and Shift+Tab exits', async () => {
312
+ const user = userEvent.setup();
313
+ const change = vi.fn();
314
+ render(
315
+ <>
316
+ <button>Before</button>
317
+ <DataTable
318
+ {...base}
319
+ selectionMode="single"
320
+ legend="Choices"
321
+ defaultSelectedId="b"
322
+ isRowDisabled={(row) => row.disabled}
323
+ onSelectedIdChange={change}
324
+ />
325
+ <button>After</button>
326
+ </>,
327
+ );
328
+ await user.tab();
329
+ await user.tab();
330
+ expect(screen.getByRole('radio', { name: 'Alpha' })).toHaveFocus();
331
+ expect(screen.getByRole('radio', { name: 'Alpha' })).toHaveAttribute('tabindex', '0');
332
+ expect(screen.getByRole('radio', { name: 'Beta' })).toBeChecked();
333
+ expect(change).not.toHaveBeenCalled();
334
+ await user.tab({ shift: true });
335
+ expect(screen.getByRole('button', { name: 'Before' })).toHaveFocus();
336
+ await user.tab();
337
+ expect(screen.getByRole('radio', { name: 'Alpha' })).toHaveFocus();
338
+ await user.tab();
339
+ expect(screen.getByRole('button', { name: 'After' })).toHaveFocus();
340
+ await user.tab({ shift: true });
341
+ expect(screen.getByRole('radio', { name: 'Alpha' })).toHaveFocus();
342
+ expect(change).not.toHaveBeenCalled();
343
+ });
344
+
345
+ it('preserves forward Tab order through headers and group controls with a checked disabled row', async () => {
346
+ const user = userEvent.setup();
347
+ const change = vi.fn();
348
+ render(
349
+ <>
350
+ <button>Before</button>
351
+ <DataTable
352
+ {...base}
353
+ columns={[{ ...columns[0]!, sortable: true }]}
354
+ selectionMode="single"
355
+ legend="Choices"
356
+ defaultSelectedId="b"
357
+ isRowDisabled={(row) => row.disabled}
358
+ onSelectedIdChange={change}
359
+ resizable
360
+ groupBy={() => 'Items'}
361
+ collapsibleGroups
362
+ />
363
+ <button>After</button>
364
+ </>,
365
+ );
366
+ await user.tab();
367
+ await user.tab();
368
+ expect(screen.getByRole('button', { name: /Name/ })).toHaveFocus();
369
+ await user.tab();
370
+ expect(screen.getByRole('separator', { name: /Name/ })).toHaveFocus();
371
+ const resize = screen.getByRole('separator', { name: /Name/ });
372
+ const width = Number(resize.getAttribute('aria-valuenow'));
373
+ await user.keyboard('{ArrowRight}');
374
+ expect(resize).toHaveFocus();
375
+ expect(resize).toHaveAttribute('aria-valuenow', String(width + 10));
376
+ expect(change).not.toHaveBeenCalled();
377
+ await user.tab();
378
+ const collapse = screen.getByRole('button', { name: /Items/ });
379
+ expect(collapse).toHaveFocus();
380
+ await user.tab();
381
+ expect(screen.getByRole('radio', { name: 'Alpha' })).toHaveFocus();
382
+ expect(screen.getByRole('radio', { name: 'Beta' })).toBeChecked();
383
+ expect(change).not.toHaveBeenCalled();
384
+ await user.tab({ shift: true });
385
+ expect(collapse).toHaveFocus();
386
+ await user.keyboard('{ArrowDown}');
387
+ expect(collapse).toHaveFocus();
388
+ expect(change).not.toHaveBeenCalled();
389
+ await user.tab();
390
+ await user.tab();
391
+ expect(screen.getByRole('button', { name: 'After' })).toHaveFocus();
392
+ await user.tab({ shift: true });
393
+ expect(screen.getByRole('radio', { name: 'Alpha' })).toHaveFocus();
394
+ });
395
+
396
+ it('skips the group when all rows are disabled and retains a checked disabled row', async () => {
397
+ const user = userEvent.setup();
398
+ const change = vi.fn();
399
+ render(
400
+ <>
401
+ <button>Before</button>
402
+ <DataTable
403
+ {...base}
404
+ selectionMode="single"
405
+ legend="Choices"
406
+ defaultSelectedId="b"
407
+ isRowDisabled={() => true}
408
+ onSelectedIdChange={change}
409
+ />
410
+ <button>After</button>
411
+ </>,
412
+ );
413
+ expect(screen.getByRole('radio', { name: 'Beta' })).toBeChecked();
414
+ for (const radio of screen.getAllByRole('radio')) {
415
+ expect(radio).toBeDisabled();
416
+ expect(radio).toHaveAttribute('tabindex', '-1');
417
+ }
418
+ await user.tab();
419
+ await user.tab();
420
+ expect(screen.getByRole('button', { name: 'After' })).toHaveFocus();
421
+ expect(change).not.toHaveBeenCalled();
422
+ });
423
+
424
+ it('does not move pointer focus to a radio when pressing cell text with a checked disabled row', async () => {
425
+ const user = userEvent.setup();
426
+ const change = vi.fn();
427
+ render(
428
+ <>
429
+ <button>Before</button>
430
+ <DataTable
431
+ {...base}
432
+ selectionMode="single"
433
+ legend="Choices"
434
+ defaultSelectedId="b"
435
+ isRowDisabled={(row) => row.disabled}
436
+ onSelectedIdChange={change}
437
+ />
438
+ </>,
439
+ );
440
+ await user.click(screen.getByRole('button', { name: 'Before' }));
441
+ await user.pointer({ target: screen.getByText('Beta', { selector: 'td' }), keys: '[MouseLeft>]' });
442
+ for (const radio of screen.getAllByRole('radio')) expect(radio).not.toHaveFocus();
443
+ await user.pointer({ keys: '[/MouseLeft]' });
444
+ expect(screen.getByRole('radio', { name: 'Beta' })).toBeChecked();
445
+ expect(change).not.toHaveBeenCalled();
446
+ });
447
+
448
+ it('gives explicit selectionMode precedence and hides group checkboxes in single mode', () => {
449
+ const { rerender } = render(<DataTable {...base} selectionMode="multiple" selectable={false} />);
450
+ expect(screen.getAllByRole('checkbox')).toHaveLength(4);
451
+ rerender(<DataTable {...base} selectionMode="none" selectable={true} />);
452
+ expect(screen.queryByRole('checkbox')).toBeNull();
453
+ expect(screen.getAllByRole('columnheader')).toHaveLength(1);
454
+ rerender(
455
+ <DataTable
456
+ {...base}
457
+ selectionMode="single"
458
+ selectable={false}
459
+ legend="Choices"
460
+ groupBy={(row) => (row.disabled ? 'Unavailable' : 'Available')}
461
+ />,
462
+ );
463
+ expect(screen.queryByRole('checkbox')).toBeNull();
464
+ expect(screen.getAllByRole('radio')).toHaveLength(3);
465
+ });
466
+
467
+ it('does not issue development warnings in production', () => {
468
+ vi.stubEnv('NODE_ENV', 'production');
469
+ const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
470
+ try {
471
+ render(
472
+ <>
473
+ <DataTable {...base} selectionMode="single" selectionName="same" />
474
+ <DataTable {...base} selectionMode="single" selectionName="same" />
475
+ </>,
476
+ );
477
+ expect(warn).not.toHaveBeenCalled();
478
+ } finally {
479
+ warn.mockRestore();
480
+ vi.unstubAllEnvs();
481
+ }
482
+ });
483
+
484
+ for (const theme of ['dark', 'light', 'high_contrast']) {
485
+ for (const allDisabled of [false, true]) {
486
+ it(`has no axe violations in ${theme} with ${allDisabled ? 'all disabled' : 'mixed'} rows`, async () => {
487
+ document.documentElement.className = theme === 'light' ? '' : theme;
488
+ const { container } = render(
489
+ <DataTable
490
+ {...base}
491
+ selectionMode="single"
492
+ legend="Available versions"
493
+ defaultSelectedId="b"
494
+ isRowDisabled={(row) => allDisabled || row.disabled}
495
+ />,
496
+ );
497
+ await expectNoAxeViolations(container);
498
+ });
499
+ }
500
+ }
501
+ });