@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,531 @@
1
+ import {
2
+ Checkbox,
3
+ cn,
4
+ DensityProvider,
5
+ Table,
6
+ TableCaption,
7
+ TableHead,
8
+ TableHeader,
9
+ TableRow,
10
+ type TableDensity,
11
+ useDensity,
12
+ useI18n,
13
+ } from '@gntik-ai/ui';
14
+ import { useId, useRef, useState } from 'react';
15
+ import { PaginationFooter } from '../PaginationFooter/PaginationFooter';
16
+ import { ColumnVisibilityMenu } from './column-menu';
17
+ import {
18
+ clampColumnWidth,
19
+ columnWidthOf,
20
+ orderColumns,
21
+ pinSideOf,
22
+ setColumnPin,
23
+ stickyTint,
24
+ tableLayout,
25
+ widthBounds,
26
+ type ColumnWidths,
27
+ type PinnedColumns,
28
+ } from './column-sizing';
29
+ import { DataTableBody, flattenBody, type DataTableBodyGroup } from './data-table-body';
30
+ import type { DataTableProps } from './data-table-props';
31
+ import {
32
+ groupKeyOf,
33
+ groupRows,
34
+ paginate,
35
+ selectionState,
36
+ sortRows,
37
+ toggleAll,
38
+ toggleOne,
39
+ type DataTableColumn,
40
+ type DataTableGroupBy,
41
+ type SortState,
42
+ } from './data-table-utils';
43
+ import { DensityToggle } from './density-toggle';
44
+ import { DEPLOYMENT_COLUMNS, DEPLOYMENT_ROWS } from './fixtures';
45
+ import { DataTableHeadCell } from './header-cell';
46
+ import { SelectionGroup, useSingleSelection, useSingleSelectionWarnings } from './single-selection';
47
+ import { useControllable, useTableViews } from './use-table-views';
48
+ import { useVirtualRows } from './use-virtual-rows';
49
+ import { ViewSwitcher, type DataTableViewState } from './views';
50
+
51
+ export type { DataTableLabels } from './labels';
52
+ export type { DataTableProps } from './data-table-props';
53
+
54
+ const defaultRowId = (row: unknown): string => {
55
+ const id = (row as { id?: unknown }).id;
56
+ return typeof id === 'string' || typeof id === 'number' ? String(id) : '';
57
+ };
58
+ const EMPTY_PINNED: PinnedColumns = {};
59
+ const EMPTY_WIDTHS: ColumnWidths = {};
60
+ const ROW_HEIGHT: Record<TableDensity, number> = { comfortable: 44, compact: 36 };
61
+
62
+ /**
63
+ * Generic data table on the kit Table: typed columns, sortable headers (aria-sort), row selection
64
+ * with select-all / indeterminate, column visibility menu, density toggle, row actions, loading
65
+ * and empty states, client-side sort + pagination, optional collapsible row groups (`groupBy`),
66
+ * column resize and pinning, row virtualization, inline cell editing and saved views.
67
+ */
68
+ export function DataTable<T = (typeof DEPLOYMENT_ROWS)[number]>({
69
+ rows = DEPLOYMENT_ROWS as unknown as readonly T[],
70
+ columns = DEPLOYMENT_COLUMNS as unknown as ReadonlyArray<DataTableColumn<T>>,
71
+ getRowId = defaultRowId,
72
+ getRowLabel,
73
+ caption = 'Deployments',
74
+ showCaption = false,
75
+ selectable: selectableProp = true,
76
+ selectionMode,
77
+ selectionName,
78
+ legend,
79
+ 'aria-label': ariaLabel,
80
+ 'aria-labelledby': ariaLabelledBy,
81
+ selectedId,
82
+ defaultSelectedId,
83
+ onSelectedIdChange,
84
+ isRowDisabled,
85
+ selectedIds,
86
+ defaultSelectedIds = [],
87
+ onSelectionChange,
88
+ bulkActions,
89
+ selectionSummary = true,
90
+ rowActions,
91
+ sort: sortProp,
92
+ defaultSort = null,
93
+ onSortChange,
94
+ manualSorting = false,
95
+ paginated: paginatedProp,
96
+ defaultPageSize = 10,
97
+ pageSizeOptions = [10, 25, 50],
98
+ density: densityProp,
99
+ defaultDensity,
100
+ onDensityChange,
101
+ showDensityToggle = true,
102
+ showColumnMenu = true,
103
+ loading = false,
104
+ loadingRows = 5,
105
+ emptyState,
106
+ groupBy,
107
+ groupLabel,
108
+ collapsibleGroups = true,
109
+ collapsedGroups: collapsedProp,
110
+ defaultCollapsedGroups = [],
111
+ onCollapsedGroupsChange,
112
+ onGroupByChange,
113
+ hiddenColumns: hiddenProp,
114
+ onHiddenColumnsChange,
115
+ resizable = false,
116
+ columnWidths: widthsProp,
117
+ defaultColumnWidths = EMPTY_WIDTHS,
118
+ onColumnWidthsChange,
119
+ pinnedColumns: pinnedProp,
120
+ defaultPinnedColumns = EMPTY_PINNED,
121
+ onPinnedColumnsChange,
122
+ showPinMenu,
123
+ virtualize = 'auto',
124
+ virtualizeThreshold = 100,
125
+ rowHeight: rowHeightProp,
126
+ virtualContainerClassName = 'max-h-[32rem]',
127
+ onCellEdit,
128
+ views: viewsProp,
129
+ defaultViews,
130
+ onViewsChange,
131
+ activeViewId,
132
+ defaultActiveViewId,
133
+ onActiveViewChange,
134
+ showViewSwitcher,
135
+ filters,
136
+ onFiltersChange,
137
+ toolbar,
138
+ className,
139
+ labels,
140
+ }: DataTableProps<T>) {
141
+ const { t } = useI18n();
142
+ const singleMode = selectionMode === 'single';
143
+ const selectable = selectionMode === undefined ? selectableProp : selectionMode === 'multiple';
144
+ const selectionColumn = selectable || singleMode;
145
+ const paginated = !singleMode && (paginatedProp ?? true);
146
+ const editHintId = useId();
147
+ const tableRef = useRef<HTMLTableElement>(null);
148
+ // A view active at mount seeds the initial state.
149
+ const [initialView] = useState(() =>
150
+ (viewsProp ?? defaultViews ?? []).find((v) => v.id === (activeViewId ?? defaultActiveViewId)),
151
+ );
152
+ const [initialFilters] = useState(() => filters);
153
+ const seed = initialView?.state ?? {};
154
+ const defaultHidden = columns.filter((c) => c.defaultHidden).map((c) => c.id);
155
+
156
+ const [innerSort, setInnerSort] = useState<SortState | null>(seed.sort !== undefined ? seed.sort : defaultSort);
157
+ const [innerSelected, setInnerSelected] = useState<ReadonlySet<string>>(() => new Set(defaultSelectedIds));
158
+ const [innerDensity, setInnerDensity] = useState<TableDensity | null>(defaultDensity ?? null);
159
+ const contextDensity = useDensity();
160
+ const [hiddenList, setHiddenList] = useControllable<readonly string[]>(
161
+ hiddenProp,
162
+ () => seed.hiddenColumns ?? defaultHidden,
163
+ (ids) => onHiddenColumnsChange?.([...ids]),
164
+ );
165
+ const [widths, setWidths] = useControllable<ColumnWidths>(
166
+ widthsProp,
167
+ () => seed.columnWidths ?? defaultColumnWidths,
168
+ (w) => onColumnWidthsChange?.({ ...w }),
169
+ );
170
+ const [pinned, setPinned] = useControllable<PinnedColumns>(
171
+ pinnedProp,
172
+ () => seed.pinnedColumns ?? defaultPinnedColumns,
173
+ onPinnedColumnsChange,
174
+ );
175
+ // `undefined`: follow the groupBy prop; a string or null: set by a saved view.
176
+ const [groupOverride, setGroupOverride] = useState<string | null | undefined>(seed.groupBy);
177
+ const [page, setPage] = useState(1);
178
+ const [pageSize, setPageSize] = useState(defaultPageSize);
179
+
180
+ const hidden: ReadonlySet<string> = new Set(hiddenList);
181
+ const effectiveGroupBy: DataTableGroupBy<T> | undefined =
182
+ groupOverride === undefined ? groupBy : ((groupOverride ?? undefined) as DataTableGroupBy<T> | undefined);
183
+ const sort = sortProp === undefined ? innerSort : sortProp;
184
+ const selected: ReadonlySet<string> = selectedIds ? new Set(selectedIds) : innerSelected;
185
+ const density = densityProp ?? innerDensity ?? contextDensity;
186
+ const visibleColumns = orderColumns(
187
+ columns.filter((c) => !hidden.has(c.id)),
188
+ pinned,
189
+ );
190
+ const labelOf =
191
+ getRowLabel ??
192
+ ((row: T) => {
193
+ const first = columns[0];
194
+ const value = first?.accessor?.(row);
195
+ return value == null ? getRowId(row) : String(value);
196
+ });
197
+
198
+ const [innerCollapsed, setInnerCollapsed] = useState<ReadonlySet<string>>(() => new Set(defaultCollapsedGroups));
199
+ const collapsed: ReadonlySet<string> = collapsedProp ? new Set(collapsedProp) : innerCollapsed;
200
+
201
+ const sortedRows = manualSorting ? [...rows] : sortRows(rows, columns, sort);
202
+ const allGroups = effectiveGroupBy ? groupRows(sortedRows, effectiveGroupBy, rows) : null;
203
+ const sorted = allGroups ? allGroups.flatMap((g) => g.rows) : sortedRows;
204
+ const pageRows = paginated ? paginate(sorted, page, pageSize) : sorted;
205
+ // Rows in collapsed groups are hidden, so select-all leaves them alone.
206
+ const pageIds = (
207
+ effectiveGroupBy ? pageRows.filter((row) => !collapsed.has(groupKeyOf(row, effectiveGroupBy))) : pageRows
208
+ ).map(getRowId);
209
+ const header = selectionState(pageIds, selected);
210
+ const selectedList = rows.map(getRowId).filter((id) => selected.has(id));
211
+
212
+ const setSelected = (next: Set<string>) => {
213
+ setInnerSelected(next);
214
+ onSelectionChange?.(rows.map(getRowId).filter((id) => next.has(id)));
215
+ };
216
+ const changeSort = (columnId: string, direction: SortState['direction']) => {
217
+ const next: SortState = { columnId, direction: sort?.columnId === columnId ? direction : 'ascending' };
218
+ setInnerSort(next);
219
+ onSortChange?.(next);
220
+ setPage(1);
221
+ };
222
+ const toggleGroup = (key: string) => {
223
+ const next = new Set(collapsed);
224
+ if (next.has(key)) next.delete(key);
225
+ else next.add(key);
226
+ setInnerCollapsed(next);
227
+ onCollapsedGroupsChange?.([...next]);
228
+ };
229
+ const nameOf = (key: string) => key || (labels?.noGroup ?? 'No group');
230
+ const bodyGroups: Array<DataTableBodyGroup<T>> | undefined =
231
+ effectiveGroupBy && allGroups
232
+ ? allGroups
233
+ .map((g) => ({
234
+ key: g.key,
235
+ label: groupLabel ? groupLabel(g.key, g.rows) : nameOf(g.key),
236
+ name: nameOf(g.key),
237
+ rows: pageRows.filter((row) => groupKeyOf(row, effectiveGroupBy) === g.key),
238
+ ids: g.rows.map(getRowId),
239
+ }))
240
+ .filter((g) => g.rows.length > 0)
241
+ : undefined;
242
+ const groupsForBody = bodyGroups && bodyGroups.length > 0 ? bodyGroups : undefined;
243
+ const singleSelection = useSingleSelection({
244
+ selectionName,
245
+ selectedId,
246
+ defaultSelectedId,
247
+ onSelectedIdChange,
248
+ getRowId,
249
+ getRowLabel: labelOf,
250
+ isRowDisabled,
251
+ rows:
252
+ !singleMode || loading
253
+ ? []
254
+ : pageRows.filter((row) => !effectiveGroupBy || !collapsed.has(groupKeyOf(row, effectiveGroupBy))),
255
+ });
256
+ useSingleSelectionWarnings({
257
+ single: singleMode,
258
+ selectionName,
259
+ paginated: paginatedProp,
260
+ hasLabel:
261
+ Boolean(legend || ariaLabel?.trim() || ariaLabelledBy?.trim()) ||
262
+ typeof legend === 'number' ||
263
+ typeof legend === 'bigint',
264
+ });
265
+
266
+ const changeDensity = (next: TableDensity) => {
267
+ setInnerDensity(next);
268
+ onDensityChange?.(next);
269
+ };
270
+ const toggleColumn = (id: string, visible: boolean) =>
271
+ setHiddenList(visible ? hiddenList.filter((c) => c !== id) : [...hiddenList, id]);
272
+ const resizeColumn = (column: DataTableColumn<T>, width: number) =>
273
+ setWidths({ ...widths, [column.id]: clampColumnWidth(column, width) });
274
+
275
+ // Saved views.
276
+ const groupName = (g: DataTableGroupBy<T> | undefined): string | null => (typeof g === 'string' ? g : null);
277
+ const currentState: DataTableViewState = {
278
+ filters,
279
+ sort,
280
+ hiddenColumns: hiddenList,
281
+ columnWidths: widths,
282
+ pinnedColumns: pinned,
283
+ groupBy: groupOverride === undefined ? groupName(groupBy) : groupOverride,
284
+ };
285
+ const defaultState: DataTableViewState = {
286
+ filters: initialFilters,
287
+ sort: defaultSort,
288
+ hiddenColumns: defaultHidden,
289
+ columnWidths: defaultColumnWidths,
290
+ pinnedColumns: defaultPinnedColumns,
291
+ groupBy: groupName(groupBy),
292
+ };
293
+ const applyState = (state: DataTableViewState) => {
294
+ const nextSort = state.sort ?? null;
295
+ setInnerSort(nextSort);
296
+ if (nextSort) onSortChange?.(nextSort);
297
+ setHiddenList(state.hiddenColumns ?? defaultHidden);
298
+ setWidths(state.columnWidths ?? defaultColumnWidths);
299
+ setPinned(state.pinnedColumns ?? defaultPinnedColumns);
300
+ const nextGroup = state.groupBy ?? null;
301
+ setGroupOverride(nextGroup === groupName(groupBy) && typeof groupBy !== 'function' ? undefined : nextGroup);
302
+ if (nextGroup !== currentState.groupBy) onGroupByChange?.(nextGroup);
303
+ if (state.filters !== filters) onFiltersChange?.(state.filters);
304
+ setPage(1);
305
+ };
306
+ const viewsApi = useTableViews({
307
+ views: viewsProp,
308
+ defaultViews,
309
+ onViewsChange,
310
+ activeViewId,
311
+ defaultActiveViewId,
312
+ onActiveViewChange,
313
+ current: currentState,
314
+ defaults: defaultState,
315
+ apply: applyState,
316
+ });
317
+ const showViews = showViewSwitcher ?? (viewsProp != null || defaultViews != null || onViewsChange != null);
318
+
319
+ const pinning =
320
+ showPinMenu ?? (pinnedProp != null || defaultPinnedColumns !== EMPTY_PINNED || onPinnedColumnsChange != null);
321
+ const layout = tableLayout({
322
+ columns: visibleColumns,
323
+ widths,
324
+ pinned,
325
+ sized: resizable || widthsProp != null || defaultColumnWidths !== EMPTY_WIDTHS,
326
+ lead: selectionColumn,
327
+ trail: rowActions != null,
328
+ });
329
+
330
+ const items = loading || pageRows.length === 0 ? 0 : flattenBody(pageRows, groupsForBody, collapsed).length;
331
+ const virtualOn =
332
+ !singleMode && !loading && (virtualize === true || (virtualize === 'auto' && items > virtualizeThreshold));
333
+ const rowHeight = rowHeightProp ?? ROW_HEIGHT[density];
334
+ const virtual = useVirtualRows({ enabled: virtualOn, count: items, rowHeight, tableRef, viewportHeight: 512 });
335
+
336
+ const hasSelection = selectable && selectedList.length > 0 && (selectionSummary || bulkActions != null);
337
+ const showToolbar = toolbar != null || showDensityToggle || showColumnMenu || showViews || hasSelection;
338
+ const anyEditable = onCellEdit != null && columns.some((c) => c.editable);
339
+
340
+ return (
341
+ <div className={cn('overflow-hidden rounded-xl border border-border bg-card shadow-sm', className)}>
342
+ {showToolbar && (
343
+ <div
344
+ className={cn(
345
+ 'flex min-h-12 flex-wrap items-center gap-2 border-b border-border px-3 py-2 transition-colors motion-reduce:transition-none',
346
+ hasSelection ? 'bg-primary/8' : 'bg-secondary/30',
347
+ )}
348
+ >
349
+ <div className="flex min-w-0 flex-1 flex-wrap items-center gap-2">
350
+ {hasSelection ? (
351
+ <>
352
+ {selectionSummary && (
353
+ <span className="text-[12.5px] font-semibold text-foreground" aria-live="polite">
354
+ {labels?.selectionCount ? (
355
+ labels.selectionCount(selectedList.length)
356
+ ) : (
357
+ <>{selectedList.length} selected</>
358
+ )}
359
+ </span>
360
+ )}
361
+ {selectionSummary && bulkActions && <span aria-hidden className="h-4 w-px bg-border" />}
362
+ {bulkActions?.(selectedList)}
363
+ </>
364
+ ) : (
365
+ toolbar
366
+ )}
367
+ </div>
368
+ <div className="flex items-center gap-2">
369
+ {showViews && (
370
+ <ViewSwitcher
371
+ labels={labels}
372
+ views={viewsApi.views}
373
+ activeId={viewsApi.activeId}
374
+ dirty={viewsApi.dirty}
375
+ onSelect={viewsApi.select}
376
+ onSaveAs={viewsApi.saveAs}
377
+ onSave={viewsApi.save}
378
+ onRename={viewsApi.rename}
379
+ onDelete={viewsApi.remove}
380
+ onReset={viewsApi.reset}
381
+ />
382
+ )}
383
+ {showDensityToggle && <DensityToggle labels={labels} value={density} onChange={changeDensity} />}
384
+ {showColumnMenu && (
385
+ <ColumnVisibilityMenu
386
+ labels={labels}
387
+ columns={columns}
388
+ hidden={hidden}
389
+ onToggle={toggleColumn}
390
+ pinOf={pinning ? (id) => pinSideOf(id, pinned) : undefined}
391
+ onPin={pinning ? (id, side) => setPinned(setColumnPin(pinned, id, side)) : undefined}
392
+ />
393
+ )}
394
+ </div>
395
+ </div>
396
+ )}
397
+ {anyEditable && (
398
+ <span id={editHintId} className="sr-only">
399
+ {labels?.editHint ?? 'Press Enter or F2 to edit.'}
400
+ </span>
401
+ )}
402
+ {/* The toggle drives a density context for the table: its rows and the controls inside them. */}
403
+ <SelectionGroup single={singleMode} legend={legend} aria-label={ariaLabel} aria-labelledby={ariaLabelledBy}>
404
+ <DensityProvider density={density}>
405
+ <Table
406
+ ref={tableRef}
407
+ aria-busy={loading || undefined}
408
+ aria-rowcount={virtual ? items + 1 : undefined}
409
+ stickyHeader={virtualOn}
410
+ containerClassName={virtualOn ? virtualContainerClassName : undefined}
411
+ containerLabel={virtualOn ? caption : labels?.scrollRegion}
412
+ // Keep single-mode resize handles inside their cells. Overflow at the
413
+ // final column would add a scroll-container stop before the header.
414
+ className={cn(
415
+ layout.tableClassName,
416
+ singleMode && '[&_[data-column-resizer]]:end-0 [&_[data-column-resizer]]:justify-end',
417
+ )}
418
+ style={layout.tableStyle}
419
+ data-virtualized={virtualOn || undefined}
420
+ >
421
+ <TableCaption srOnly={!showCaption}>{caption}</TableCaption>
422
+ <TableHeader>
423
+ <TableRow className="hover:bg-transparent" aria-rowindex={virtual ? 1 : undefined}>
424
+ {selectionColumn && (
425
+ <TableHead
426
+ className={cn(
427
+ 'w-10 pe-0',
428
+ layout.lead.className,
429
+ layout.lead.pinned && 'z-[11] bg-card',
430
+ stickyTint(layout.lead, { head: true }),
431
+ )}
432
+ style={layout.lead.style}
433
+ >
434
+ {singleMode && <span className="sr-only">{labels?.selection ?? t('table.selection')}</span>}
435
+ {selectable && (
436
+ <Checkbox
437
+ aria-label={labels?.selectAll ?? t('table.selectAllPage')}
438
+ checked={header.checked}
439
+ indeterminate={header.indeterminate}
440
+ disabled={loading || pageIds.length === 0}
441
+ onCheckedChange={() => setSelected(toggleAll(pageIds, selected))}
442
+ />
443
+ )}
444
+ </TableHead>
445
+ )}
446
+ {visibleColumns.map((c) => {
447
+ const bounds = widthBounds(c);
448
+ return (
449
+ <DataTableHeadCell
450
+ labels={labels}
451
+ key={c.id}
452
+ column={c}
453
+ layout={layout.columns.get(c.id)}
454
+ sortDirection={sort?.columnId === c.id ? sort.direction : 'none'}
455
+ onSortChange={(dir) => changeSort(c.id, dir)}
456
+ resize={
457
+ resizable && c.resizable !== false
458
+ ? {
459
+ width: columnWidthOf(c, widths),
460
+ min: bounds.min,
461
+ max: bounds.max,
462
+ onResize: (w) => resizeColumn(c, w),
463
+ }
464
+ : undefined
465
+ }
466
+ />
467
+ );
468
+ })}
469
+ {rowActions && (
470
+ <TableHead
471
+ className={cn(
472
+ 'w-12',
473
+ layout.trail.className,
474
+ layout.trail.pinned && 'z-[11] bg-card',
475
+ stickyTint(layout.trail, { head: true }),
476
+ )}
477
+ style={layout.trail.style}
478
+ >
479
+ <span className="sr-only">{labels?.actions ?? t('table.actions')}</span>
480
+ </TableHead>
481
+ )}
482
+ </TableRow>
483
+ </TableHeader>
484
+ <DataTableBody
485
+ labels={labels}
486
+ rows={pageRows}
487
+ columns={visibleColumns}
488
+ getRowId={getRowId}
489
+ getRowLabel={labelOf}
490
+ selectable={selectable}
491
+ selected={selected}
492
+ singleSelection={singleMode ? singleSelection : undefined}
493
+ onToggleRow={(id) => setSelected(toggleOne(id, selected))}
494
+ rowActions={rowActions}
495
+ loading={loading}
496
+ loadingRows={loadingRows}
497
+ emptyState={emptyState}
498
+ groups={groupsForBody}
499
+ collapsedGroups={collapsed}
500
+ collapsibleGroups={collapsibleGroups}
501
+ onToggleGroup={toggleGroup}
502
+ onToggleGroupRows={(ids) => setSelected(toggleAll(ids, selected))}
503
+ layout={layout}
504
+ onCellEdit={onCellEdit}
505
+ editHintId={anyEditable ? editHintId : undefined}
506
+ virtual={virtual}
507
+ />
508
+ </Table>
509
+ </DensityProvider>
510
+ </SelectionGroup>
511
+ {loading && (
512
+ <p role="status" className="sr-only">
513
+ {labels?.loading ? labels.loading(caption) : <>Loading {caption.toLowerCase()}…</>}
514
+ </p>
515
+ )}
516
+ {paginated && !loading && rows.length > 0 && (
517
+ <PaginationFooter
518
+ labels={labels}
519
+ className="border-t border-border px-4 py-3"
520
+ total={rows.length}
521
+ page={page}
522
+ onPageChange={setPage}
523
+ pageSize={pageSize}
524
+ onPageSizeChange={setPageSize}
525
+ pageSizeOptions={pageSizeOptions}
526
+ noun={caption}
527
+ />
528
+ )}
529
+ </div>
530
+ );
531
+ }
@@ -0,0 +1,129 @@
1
+ import type { BlockMeta } from '../../meta';
2
+ import type { DataTableLabels } from './labels';
3
+
4
+ export const meta: BlockMeta = {
5
+ name: 'Data table',
6
+ family: 'tables',
7
+ status: 'beta',
8
+ description:
9
+ 'Generic typed table: sortable headers with aria-sort, row selection with select-all and indeterminate by default (selectionMode="multiple"), selectionMode="none" to hide selection, column visibility menu, density toggle, row actions, loading and empty states, client-side sort and pagination, optional row groups (groupBy) with rowgroup header rows, counts, keyboard-accessible collapse buttons and group selection; column resize (pointer drag or a focusable role="separator" handle with arrow keys, min/max widths, controlled columnWidths), column pinning to either edge with sticky cells (column menu or pinnedColumns), row virtualization for large datasets (aria-rowcount/aria-rowindex, works with selection and grouping), inline cell editing (text, number and select editors; Enter/F2 to edit, Enter/Tab to commit, Escape to cancel, onCellEdit can reject with an inline error) and saved views (filters, sort, visible columns, widths, pinning and grouping) with a view switcher to save as, rename, delete and reset. ' +
10
+ 'Opt-in selectionMode="single" uses native radios in a labelled fieldset (never paginated or virtualized; paginated defaults to false and paginated=true is a type error). Single mode uses selectedId/onSelectedIdChange or defaultSelectedId; isRowDisabled skips unavailable rows and preserves a checked disabled choice without callbacks. Supply legend, aria-label or aria-labelledby for the group and getRowLabel for each radio (otherwise the first column labels it). selectionName is used verbatim and must be unique per page; omitted names use an instance-unique useId name, and duplicate supplied names or missing group labels warn in development. Tab enters once on the checked enabled row or first enabled row; arrows wrap and skip disabled rows, Space selects, Shift+Tab exits, and all-disabled groups have no tab stop. No select-all, group checkboxes, bulk actions or selection summary appear in single mode. ' +
11
+ 'Examples in examples/single-selection.tsx: an uncontrolled non-paginated list with legend="Available items" and a controlled versions table with legend="Available versions", selectedId="v2" and a checked disabled version.',
12
+
13
+ labels: [
14
+ {
15
+ key: 'scrollRegion',
16
+ type: 'string',
17
+ state: 'Non-virtual table overflow: focusable scroll region name; virtualized regions use the caller caption',
18
+ },
19
+ {
20
+ key: 'selectionCount',
21
+ type: '(count: number) => string',
22
+ state: 'Multiple selection: toolbar count and polite live region',
23
+ },
24
+ { key: 'columns', type: 'string', state: 'Column visibility trigger text and name' },
25
+ { key: 'visibleColumns', type: 'string', state: 'Open column menu: visibility heading' },
26
+ { key: 'pinColumns', type: 'string', state: 'Open column menu with pinning: pin heading' },
27
+ { key: 'pinnedLeft', type: 'string', state: 'Open pin submenu trigger: left side suffix' },
28
+ { key: 'pinnedRight', type: 'string', state: 'Open pin submenu trigger: right side suffix' },
29
+ { key: 'pinLeft', type: 'string', state: 'Open pin submenu: left option' },
30
+ { key: 'pinRight', type: 'string', state: 'Open pin submenu: right option' },
31
+ { key: 'unpinned', type: 'string', state: 'Open pin submenu: unpinned option' },
32
+ { key: 'density', type: 'string', state: 'Density group accessible name' },
33
+ { key: 'comfortable', type: 'string', state: 'Comfortable density button name' },
34
+ { key: 'compact', type: 'string', state: 'Compact density button name' },
35
+ {
36
+ key: 'resizeColumn',
37
+ type: '(label: string) => string',
38
+ state: 'Resizable header: separator name; label is caller column header',
39
+ },
40
+ {
41
+ key: 'columnWidth',
42
+ type: '(width: number) => string',
43
+ state: 'Resizable header: separator aria-valuetext in pixels',
44
+ },
45
+ { key: 'defaultView', type: 'string', state: 'View trigger when no active view' },
46
+ { key: 'defaultViewOption', type: 'string', state: 'Open views menu: default option' },
47
+ { key: 'modified', type: 'string', state: 'Dirty view trigger: screen reader suffix' },
48
+ { key: 'views', type: 'string', state: 'Open views menu: heading' },
49
+ { key: 'saveChanges', type: 'string', state: 'Open views menu: save current view action' },
50
+ { key: 'saveAs', type: 'string', state: 'Open views menu: save as action' },
51
+ { key: 'renameView', type: 'string', state: 'Open views menu: rename action' },
52
+ { key: 'deleteView', type: 'string', state: 'Open views menu: immediate delete action; no confirmation dialog' },
53
+ { key: 'resetChanges', type: 'string', state: 'Open views menu: reset action' },
54
+ { key: 'saveViewTitle', type: 'string', state: 'Save as dialog: title and accessible name' },
55
+ { key: 'renameViewTitle', type: 'string', state: 'Rename dialog: title and accessible name' },
56
+ { key: 'viewName', type: 'string', state: 'Save as and Rename dialogs: input label' },
57
+ { key: 'cancel', type: 'string', state: 'Save as and Rename dialogs: cancel button' },
58
+ { key: 'saveView', type: 'string', state: 'Save as dialog: submit button' },
59
+ { key: 'rename', type: 'string', state: 'Rename dialog: submit button' },
60
+ { key: 'closeDialog', type: 'string', state: 'Save as and Rename dialogs: close button name' },
61
+ { key: 'selection', type: 'string', state: 'Single selection: screen reader column heading' },
62
+ { key: 'selectAll', type: 'string', state: 'Multiple selection: header checkbox name' },
63
+ {
64
+ key: 'selectRow',
65
+ type: '(label: string) => string',
66
+ state: 'Multiple selection: row checkbox name; label is caller row name',
67
+ },
68
+ { key: 'selectGroup', type: '(group: string) => string', state: 'Grouped multiple selection: group checkbox name' },
69
+ {
70
+ key: 'groupRows',
71
+ type: '(count: number) => string',
72
+ state: 'Grouped header: screen reader row-count unit; numeric count stays unchanged',
73
+ },
74
+ { key: 'noGroup', type: 'string', state: 'Grouped header: fallback when group key is empty' },
75
+ { key: 'actions', type: 'string', state: 'Row actions present: screen reader column heading' },
76
+ {
77
+ key: 'rowActions',
78
+ type: '(label: string) => string',
79
+ state: 'Row action menu trigger name; label is caller row name',
80
+ },
81
+ { key: 'emptyTitle', type: 'string', state: 'Empty rows: default empty-state title' },
82
+ { key: 'emptyDescription', type: 'string', state: 'Empty rows: default empty-state description' },
83
+ {
84
+ key: 'loading',
85
+ type: '(caption: string) => string',
86
+ state: 'Loading: status announcement; receives original caller caption',
87
+ },
88
+ { key: 'editHint', type: 'string', state: 'Editable table: shared keyboard instruction description' },
89
+ { key: 'editLabel', type: '(column: string, row: string) => string', state: 'Editing cell: input accessible name' },
90
+ { key: 'editError', type: 'string', state: 'Rejected cell edit without caller error message: alert' },
91
+ { key: 'enterNumber', type: 'string', state: 'Editing number: invalid numeric value alert' },
92
+ { key: 'minimumValue', type: '(min: number) => string', state: 'Editing number below minimum: alert' },
93
+ { key: 'maximumValue', type: '(max: number) => string', state: 'Editing number above maximum: alert' },
94
+ { key: 'rowsPerPage', type: 'string', state: 'Always: visible page-size text' },
95
+ { key: 'rowsPerPageLabel', type: 'string', state: 'Always: page-size Select accessible name' },
96
+ {
97
+ key: 'rangeSummary',
98
+ type: '(from: number, to: number, total: number) => string',
99
+ state: 'Every page and page-size change: visible summary and polite live region',
100
+ },
101
+ {
102
+ key: 'paginationNav',
103
+ type: '(noun: string) => string',
104
+ state: 'Always: navigation accessible name; noun is the caller caption',
105
+ },
106
+ { key: 'previous', type: 'string', state: 'Always: previous-page button name' },
107
+ { key: 'next', type: 'string', state: 'Always: next-page button name' },
108
+ {
109
+ key: 'page',
110
+ type: '(page: number) => string',
111
+ state: 'Always: numbered-page button name; number stays caller content',
112
+ },
113
+ ] satisfies Array<{ key: keyof DataTableLabels; type: string; state: string }>,
114
+ uses: [
115
+ 'Table',
116
+ 'Checkbox',
117
+ 'Menu',
118
+ 'MoreMenu',
119
+ 'ToggleGroup',
120
+ 'Button',
121
+ 'Skeleton',
122
+ 'EmptyState',
123
+ 'Pagination',
124
+ 'SimpleSelect',
125
+ 'StatusTag',
126
+ 'Input',
127
+ 'Dialog',
128
+ ],
129
+ };