@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,231 @@
1
+ import { ChevronRight } from '@gntik-ai/icons';
2
+ import { Checkbox, cn, MoreMenu, TableCell, TableHead, TableRow, type MoreMenuAction, useI18n } from '@gntik-ai/ui';
3
+ import type { CSSProperties, ReactNode } from 'react';
4
+ import { stickyTint, type TableLayout } from './column-sizing';
5
+ import {
6
+ COLUMN_VARIANT_CLASSES,
7
+ columnAlign,
8
+ formatCell,
9
+ selectionState,
10
+ type DataTableColumn,
11
+ } from './data-table-utils';
12
+ import { EditableCell, isCellEditable, type DataTableCellEditHandler } from './editable-cell';
13
+ import type { DataTableLabels } from './labels';
14
+ import type { SingleSelection } from './single-selection';
15
+
16
+ /** A group as the body renders it: its rows on the current page plus header data. */
17
+ export interface DataTableBodyGroup<T> {
18
+ key: string;
19
+ /** Visible group name (header text). */
20
+ label: ReactNode;
21
+ /** Plain-text name for button and checkbox labels. */
22
+ name: string;
23
+ /** Rows of the group on the current page. */
24
+ rows: readonly T[];
25
+ /** Ids of every row in the group (all pages): the count and the group checkbox use them. */
26
+ ids: readonly string[];
27
+ }
28
+
29
+ /** Row rendering shared by the plain, grouped and virtualized bodies. */
30
+ export interface RowContext<T> {
31
+ labels?: Partial<DataTableLabels>;
32
+ columns: ReadonlyArray<DataTableColumn<T>>;
33
+ getRowId: (row: T) => string;
34
+ getRowLabel: (row: T) => string;
35
+ selectable: boolean;
36
+ selected: ReadonlySet<string>;
37
+ singleSelection?: SingleSelection<T>;
38
+ onToggleRow: (id: string) => void;
39
+ rowActions?: (row: T) => Array<MoreMenuAction | 'separator'>;
40
+ layout?: TableLayout;
41
+ onCellEdit?: DataTableCellEditHandler<T>;
42
+ /** Id of the "Press Enter or F2 to edit" hint. */
43
+ editHintId?: string;
44
+ }
45
+
46
+ export interface DataTableRowProps<T> extends RowContext<T> {
47
+ row: T;
48
+ /** 1-based row index in the whole table (virtualized tables). */
49
+ ariaRowIndex?: number;
50
+ style?: CSSProperties;
51
+ }
52
+
53
+ export function DataTableRow<T>({
54
+ labels,
55
+ row,
56
+ columns,
57
+ getRowId,
58
+ getRowLabel,
59
+ selectable,
60
+ selected,
61
+ singleSelection,
62
+ onToggleRow,
63
+ rowActions,
64
+ layout,
65
+ onCellEdit,
66
+ editHintId,
67
+ ariaRowIndex,
68
+ style,
69
+ }: DataTableRowProps<T>) {
70
+ const { t } = useI18n();
71
+ const id = getRowId(row);
72
+ const selectionColumn = selectable || singleSelection != null;
73
+ const isSelected = singleSelection ? singleSelection.selectedId === id : selected.has(id);
74
+ const label = getRowLabel(row);
75
+ const tint = (l: TableLayout['lead'] | undefined) => stickyTint(l, { selected: selectionColumn && isSelected });
76
+ return (
77
+ <TableRow selected={selectionColumn && isSelected} aria-rowindex={ariaRowIndex} data-row-id={id} style={style}>
78
+ {selectionColumn && (
79
+ <TableCell className={cn('w-10 pe-0', layout?.lead.className, tint(layout?.lead))} style={layout?.lead.style}>
80
+ {singleSelection ? (
81
+ <span {...singleSelection.entryProps(row)} className="inline-flex">
82
+ <input
83
+ {...singleSelection.inputProps(row)}
84
+ className="size-4 accent-primary focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus-ring disabled:cursor-not-allowed"
85
+ />
86
+ </span>
87
+ ) : (
88
+ <Checkbox
89
+ aria-label={labels?.selectRow?.(label) ?? t('table.selectRow', { label })}
90
+ checked={isSelected}
91
+ onCheckedChange={() => onToggleRow(id)}
92
+ />
93
+ )}
94
+ </TableCell>
95
+ )}
96
+ {columns.map((c) => {
97
+ const cl = layout?.columns.get(c.id);
98
+ const content = c.cell ? c.cell(row) : formatCell(c.accessor?.(row));
99
+ const editable = onCellEdit && editHintId && isCellEditable(c, row);
100
+ return (
101
+ <TableCell
102
+ key={c.id}
103
+ align={columnAlign(c)}
104
+ style={cl?.style}
105
+ className={cn(
106
+ 'whitespace-nowrap',
107
+ cl?.style && 'overflow-hidden text-ellipsis',
108
+ COLUMN_VARIANT_CLASSES[c.variant ?? 'default'],
109
+ c.className,
110
+ cl?.className,
111
+ tint(cl),
112
+ )}
113
+ >
114
+ {editable ? (
115
+ <EditableCell
116
+ labels={labels}
117
+ row={row}
118
+ rowId={id}
119
+ rowLabel={label}
120
+ column={c}
121
+ onCellEdit={onCellEdit}
122
+ hintId={editHintId}
123
+ >
124
+ {content}
125
+ </EditableCell>
126
+ ) : (
127
+ content
128
+ )}
129
+ </TableCell>
130
+ );
131
+ })}
132
+ {rowActions && (
133
+ <TableCell
134
+ align="right"
135
+ className={cn('w-12 py-0', layout?.trail.className, tint(layout?.trail))}
136
+ style={layout?.trail.style}
137
+ >
138
+ <MoreMenu
139
+ items={rowActions(row)}
140
+ label={labels?.rowActions?.(label) ?? t('table.rowActions', { label })}
141
+ variant="ghost"
142
+ size="sm"
143
+ />
144
+ </TableCell>
145
+ )}
146
+ </TableRow>
147
+ );
148
+ }
149
+
150
+ export const groupHeadClass =
151
+ 'h-9 bg-secondary/50 text-[12.5px] font-semibold tracking-normal normal-case text-foreground';
152
+
153
+ export interface GroupHeaderRowProps<T> {
154
+ labels?: Partial<DataTableLabels>;
155
+ group: DataTableBodyGroup<T>;
156
+ open: boolean;
157
+ selectable: boolean;
158
+ selected: ReadonlySet<string>;
159
+ /** Columns spanned by the header cell (every column but the selection one). */
160
+ span: number;
161
+ collapsible: boolean;
162
+ onToggle?: (key: string) => void;
163
+ onToggleRows?: (ids: readonly string[]) => void;
164
+ ariaRowIndex?: number;
165
+ style?: CSSProperties;
166
+ }
167
+
168
+ /** The header row of a group: group checkbox, collapse button, label and count. */
169
+ export function GroupHeaderRow<T>({
170
+ labels,
171
+ group,
172
+ open,
173
+ selectable,
174
+ selected,
175
+ span,
176
+ collapsible,
177
+ onToggle,
178
+ onToggleRows,
179
+ ariaRowIndex,
180
+ style,
181
+ }: GroupHeaderRowProps<T>) {
182
+ const { t } = useI18n();
183
+ const state = selectionState(group.ids, selected);
184
+ const count = group.ids.length;
185
+ const heading = (
186
+ <>
187
+ <span className="truncate">{group.label}</span>{' '}
188
+ <span className="rounded-full bg-secondary px-1.5 font-mono text-[11px] font-medium text-muted-foreground tabular-nums">
189
+ {count}
190
+ </span>{' '}
191
+ <span className="sr-only">{labels?.groupRows?.(count) ?? (count === 1 ? 'row' : 'rows')}</span>
192
+ </>
193
+ );
194
+ return (
195
+ <TableRow className="hover:bg-transparent" aria-rowindex={ariaRowIndex} style={style} data-group-header={group.key}>
196
+ {selectable && (
197
+ <TableCell className={cn('w-10 pe-0', groupHeadClass)}>
198
+ <Checkbox
199
+ aria-label={labels?.selectGroup?.(group.name) ?? t('table.selectRow', { label: `all in ${group.name}` })}
200
+ checked={state.checked}
201
+ indeterminate={state.indeterminate}
202
+ disabled={count === 0}
203
+ onCheckedChange={() => onToggleRows?.(group.ids)}
204
+ />
205
+ </TableCell>
206
+ )}
207
+ <TableHead scope="rowgroup" colSpan={span} className={cn(groupHeadClass, collapsible && 'ps-2')}>
208
+ {collapsible ? (
209
+ <button
210
+ type="button"
211
+ aria-expanded={open}
212
+ onClick={() => onToggle?.(group.key)}
213
+ className="inline-flex max-w-full items-center gap-1.5 rounded-sm px-1 py-0.5 hover:bg-secondary focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus-ring"
214
+ >
215
+ <ChevronRight
216
+ size={14}
217
+ aria-hidden
218
+ className={cn(
219
+ 'shrink-0 text-muted-foreground transition-transform motion-reduce:transition-none',
220
+ open ? 'rotate-90' : 'rtl:-scale-x-100',
221
+ )}
222
+ />
223
+ {heading}
224
+ </button>
225
+ ) : (
226
+ <span className="inline-flex max-w-full items-center gap-1.5">{heading}</span>
227
+ )}
228
+ </TableHead>
229
+ </TableRow>
230
+ );
231
+ }
@@ -0,0 +1,195 @@
1
+ import type { ReactNode } from 'react';
2
+
3
+ export type SortDirectionValue = 'ascending' | 'descending';
4
+ export type CellValue = string | number | boolean | Date | null | undefined;
5
+
6
+ export type DataTableColumnVariant = 'default' | 'mono' | 'muted' | 'numeric';
7
+
8
+ /** Body-cell classes of each column variant. */
9
+ export const COLUMN_VARIANT_CLASSES: Record<DataTableColumnVariant, string> = {
10
+ default: '',
11
+ mono: 'font-mono text-[12px]',
12
+ muted: 'text-[12.5px] text-muted-foreground',
13
+ numeric: 'font-mono text-[12.5px] tabular-nums',
14
+ };
15
+
16
+ /** Effective alignment of a column (numeric columns default to right). */
17
+ export function columnAlign<T>(column: DataTableColumn<T>): DataTableColumn<T>['align'] {
18
+ return column.align ?? (column.variant === 'numeric' ? 'right' : undefined);
19
+ }
20
+
21
+ /** One typed column of a DataTable. */
22
+ export interface DataTableColumn<T> {
23
+ /** Stable key (sorting, visibility). */
24
+ id: string;
25
+ /** Header text; also the column's name in the visibility menu. */
26
+ header: string;
27
+ /** Raw value: used to sort, and rendered when `cell` is absent. */
28
+ accessor?: (row: T) => CellValue;
29
+ /** Custom cell rendering. */
30
+ cell?: (row: T) => ReactNode;
31
+ sortable?: boolean;
32
+ align?: 'left' | 'right' | 'center';
33
+ /** Can be hidden from the column menu (default true). */
34
+ hideable?: boolean;
35
+ /** Hidden until turned on in the column menu. */
36
+ defaultHidden?: boolean;
37
+ /**
38
+ * Presentational preset for the body cells: `mono` (monospace ids, regions), `muted`
39
+ * (secondary text), `numeric` (monospace tabular figures, right-aligned unless `align` is set).
40
+ */
41
+ variant?: DataTableColumnVariant;
42
+ /** Classes for the body cells (merged after the variant's). */
43
+ className?: string;
44
+ /** Classes for the header cell (e.g. a width). */
45
+ headClassName?: string;
46
+
47
+ /** Initial width in px (resizable or pinned tables; default 160). */
48
+ width?: number;
49
+ /** Smallest width a resize can reach (default 60). */
50
+ minWidth?: number;
51
+ /** Largest width a resize can reach (default 640). */
52
+ maxWidth?: number;
53
+ /** Shows a resize handle when the table is `resizable` (default true). */
54
+ resizable?: boolean;
55
+ /** Can be pinned from the column menu (default true). */
56
+ pinnable?: boolean;
57
+
58
+ /** Inline editing: Enter or F2 on the cell edits it (needs `onCellEdit` on the table). */
59
+ editable?: boolean | ((row: T) => boolean);
60
+ /** Editor of an editable cell (default a text input). */
61
+ editor?: DataTableCellEditor;
62
+ /** Value the editor starts from (default the accessor's value). */
63
+ editValue?: (row: T) => string | number | null | undefined;
64
+ }
65
+
66
+ /** Editor of an editable column. */
67
+ export type DataTableCellEditor =
68
+ | { type: 'text'; placeholder?: string }
69
+ | { type: 'number'; min?: number; max?: number; step?: number }
70
+ | { type: 'select'; options: ReadonlyArray<{ value: string; label: string }> };
71
+
72
+ export interface SortState {
73
+ columnId: string;
74
+ direction: SortDirectionValue;
75
+ }
76
+
77
+ /** Compares two cell values: nulls last, numbers and dates numerically, strings with localeCompare (numeric). */
78
+ export function compareValues(a: CellValue, b: CellValue): number {
79
+ if (a == null && b == null) return 0;
80
+ if (a == null) return 1;
81
+ if (b == null) return -1;
82
+ const na = a instanceof Date ? a.getTime() : a;
83
+ const nb = b instanceof Date ? b.getTime() : b;
84
+ if (typeof na === 'number' && typeof nb === 'number') return na - nb;
85
+ if (typeof na === 'boolean' && typeof nb === 'boolean') return Number(na) - Number(nb);
86
+ return String(na).localeCompare(String(nb), undefined, { numeric: true, sensitivity: 'base' });
87
+ }
88
+
89
+ /** Client-side sort (stable). Returns the input order when there is no sort or the column has no accessor. */
90
+ export function sortRows<T>(
91
+ rows: readonly T[],
92
+ columns: ReadonlyArray<DataTableColumn<T>>,
93
+ sort: SortState | null,
94
+ ): T[] {
95
+ const column = sort ? columns.find((c) => c.id === sort.columnId) : undefined;
96
+ if (!sort || !column?.accessor) return [...rows];
97
+ const get = column.accessor;
98
+ const sign = sort.direction === 'ascending' ? 1 : -1;
99
+ return rows
100
+ .map((row, index) => ({ row, index }))
101
+ .sort((x, y) => {
102
+ const va = get(x.row);
103
+ const vb = get(y.row);
104
+ // Nulls stay last in both directions.
105
+ if (va == null || vb == null) return compareValues(va, vb) || x.index - y.index;
106
+ return sign * compareValues(va, vb) || x.index - y.index;
107
+ })
108
+ .map(({ row }) => row);
109
+ }
110
+
111
+ /** Number of pages for a row count (at least 1). */
112
+ export function pageCountOf(total: number, pageSize: number): number {
113
+ return Math.max(1, Math.ceil(total / Math.max(1, pageSize)));
114
+ }
115
+
116
+ /** Client-side pagination: the rows of a 1-based page (clamped to the valid range). */
117
+ export function paginate<T>(rows: readonly T[], page: number, pageSize: number): T[] {
118
+ const last = pageCountOf(rows.length, pageSize);
119
+ const p = Math.min(Math.max(1, page), last);
120
+ return rows.slice((p - 1) * pageSize, p * pageSize);
121
+ }
122
+
123
+ /** Header checkbox state for the rows on screen. */
124
+ export function selectionState(
125
+ visibleIds: readonly string[],
126
+ selected: ReadonlySet<string>,
127
+ ): { checked: boolean; indeterminate: boolean } {
128
+ const count = visibleIds.filter((id) => selected.has(id)).length;
129
+ return { checked: count > 0 && count === visibleIds.length, indeterminate: count > 0 && count < visibleIds.length };
130
+ }
131
+
132
+ /** Selects all of `ids` when not all are selected; otherwise clears them. Other selections are kept. */
133
+ export function toggleAll(ids: readonly string[], selected: ReadonlySet<string>): Set<string> {
134
+ const next = new Set(selected);
135
+ const all = ids.length > 0 && ids.every((id) => selected.has(id));
136
+ for (const id of ids) {
137
+ if (all) next.delete(id);
138
+ else next.add(id);
139
+ }
140
+ return next;
141
+ }
142
+
143
+ export function toggleOne(id: string, selected: ReadonlySet<string>): Set<string> {
144
+ const next = new Set(selected);
145
+ if (next.has(id)) next.delete(id);
146
+ else next.add(id);
147
+ return next;
148
+ }
149
+
150
+ /** Default cell text for a raw value. */
151
+ export function formatCell(value: CellValue): ReactNode {
152
+ if (value == null) return '—';
153
+ if (value instanceof Date) return value.toLocaleDateString();
154
+ if (typeof value === 'boolean') return value ? 'Yes' : 'No';
155
+ return value;
156
+ }
157
+
158
+ /** Groups rows by a field key or by a function returning the group key. */
159
+ export type DataTableGroupBy<T> = Extract<keyof T, string> | ((row: T) => string | number | null | undefined);
160
+
161
+ /** One group of rows, in display order. */
162
+ export interface DataTableGroup<T> {
163
+ /** Group key (`''` for rows without a value). */
164
+ key: string;
165
+ rows: T[];
166
+ }
167
+
168
+ /** Group key of a row (`''` when the value is null or undefined). */
169
+ export function groupKeyOf<T>(row: T, groupBy: DataTableGroupBy<T>): string {
170
+ const value = typeof groupBy === 'function' ? groupBy(row) : (row as Record<string, unknown>)[groupBy];
171
+ return value == null ? '' : String(value);
172
+ }
173
+
174
+ /**
175
+ * Splits rows into groups, keeping the row order inside each group. Groups follow `order` (the
176
+ * keys' first appearance in it, e.g. the unsorted input) and then any key not seen there.
177
+ */
178
+ export function groupRows<T>(
179
+ rows: readonly T[],
180
+ groupBy: DataTableGroupBy<T>,
181
+ order: readonly T[] = rows,
182
+ ): DataTableGroup<T>[] {
183
+ const groups = new Map<string, T[]>();
184
+ for (const row of order) {
185
+ const key = groupKeyOf(row, groupBy);
186
+ if (!groups.has(key)) groups.set(key, []);
187
+ }
188
+ for (const row of rows) {
189
+ const key = groupKeyOf(row, groupBy);
190
+ const list = groups.get(key);
191
+ if (list) list.push(row);
192
+ else groups.set(key, [row]);
193
+ }
194
+ return [...groups].filter(([, list]) => list.length > 0).map(([key, list]) => ({ key, rows: list }));
195
+ }
@@ -0,0 +1,33 @@
1
+ import { Rows2, Rows3 } from '@gntik-ai/icons';
2
+ import { Toggle, ToggleGroup, type TableDensity, useI18n } from '@gntik-ai/ui';
3
+
4
+ import type { DataTableLabels } from './labels';
5
+
6
+ export interface DensityToggleProps {
7
+ labels?: Partial<DataTableLabels>;
8
+ value: TableDensity;
9
+ onChange: (density: TableDensity) => void;
10
+ }
11
+
12
+ /** Two-icon segmented control: comfortable or compact rows. */
13
+ export function DensityToggle({ value, onChange, labels }: DensityToggleProps) {
14
+ const { t } = useI18n();
15
+ return (
16
+ <ToggleGroup
17
+ aria-label={labels?.density ?? t('table.density')}
18
+ size="sm"
19
+ value={[value]}
20
+ onValueChange={(v) => {
21
+ const next = v[0];
22
+ if (next === 'compact' || next === 'comfortable') onChange(next);
23
+ }}
24
+ >
25
+ <Toggle value="comfortable" iconOnly aria-label={labels?.comfortable ?? t('table.comfortable')}>
26
+ <Rows2 size={15} aria-hidden />
27
+ </Toggle>
28
+ <Toggle value="compact" iconOnly aria-label={labels?.compact ?? t('table.compact')}>
29
+ <Rows3 size={15} aria-hidden />
30
+ </Toggle>
31
+ </ToggleGroup>
32
+ );
33
+ }