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