@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
package/src/meta.ts ADDED
@@ -0,0 +1,23 @@
1
+ /** Metadata contract for a block: packages/blocks/src/<family>/<Name>/block.meta.ts. */
2
+ export interface BlockMeta {
3
+ /** Display name, e.g. "KPI row". */
4
+ name: string;
5
+ family:
6
+ | 'page-chrome'
7
+ | 'shell'
8
+ | 'data-display'
9
+ | 'tables'
10
+ | 'forms'
11
+ | 'feedback'
12
+ | 'billing'
13
+ | 'team'
14
+ | 'scheduling'
15
+ | 'builders'
16
+ | 'ai'
17
+ | 'auth'
18
+ | 'marketing';
19
+ status: 'stable' | 'beta' | 'experimental';
20
+ description: string;
21
+ /** @gntik-ai/ui (and other kit) components it composes, for docs and agents. */
22
+ uses: string[];
23
+ }
@@ -0,0 +1,34 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import userEvent from '@testing-library/user-event';
3
+ import { expectNoAxeViolations } from '../../test/a11y';
4
+ import { BulkActionBar } from './BulkActionBar';
5
+
6
+ describe('BulkActionBar', () => {
7
+ it('renders the default selection', async () => {
8
+ const { container } = render(<BulkActionBar totalCount={24} />);
9
+ expect(screen.getByRole('toolbar', { name: 'Bulk actions' })).toBeInTheDocument();
10
+ expect(screen.getByRole('status')).toHaveTextContent('3 of 24 items selected');
11
+ await expectNoAxeViolations(container);
12
+ });
13
+
14
+ it('runs actions, clears and hides at zero', async () => {
15
+ const user = userEvent.setup();
16
+ const onClear = vi.fn();
17
+ const onArchive = vi.fn();
18
+ const { rerender } = render(
19
+ <BulkActionBar
20
+ selectedCount={1}
21
+ noun={['member', 'members']}
22
+ onClear={onClear}
23
+ actions={[{ label: 'Archive', onClick: onArchive }]}
24
+ />,
25
+ );
26
+ expect(screen.getByRole('status')).toHaveTextContent('1 member selected');
27
+ await user.click(screen.getByRole('button', { name: 'Archive' }));
28
+ await user.click(screen.getByRole('button', { name: 'Clear selection' }));
29
+ expect(onArchive).toHaveBeenCalledTimes(1);
30
+ expect(onClear).toHaveBeenCalledTimes(1);
31
+ rerender(<BulkActionBar selectedCount={0} />);
32
+ expect(screen.queryByRole('toolbar')).not.toBeInTheDocument();
33
+ });
34
+ });
@@ -0,0 +1,76 @@
1
+ import { Archive, Download, Trash2, X } from '@gntik-ai/icons';
2
+ import { Toolbar, ToolbarButton, ToolbarEnd, ToolbarSeparator, ToolbarStart, cn, useI18n } from '@gntik-ai/ui';
3
+ import type { PageChromeAction } from '../types';
4
+
5
+ export interface BulkActionBarProps {
6
+ /** Number of selected rows; the bar renders nothing at 0. */
7
+ selectedCount?: number;
8
+ /** Total rows, shown as "3 of 24 selected". */
9
+ totalCount?: number;
10
+ /** Noun for the count, singular and plural. */
11
+ noun?: [singular: string, plural: string];
12
+ actions?: PageChromeAction[];
13
+ onClear?: () => void;
14
+ clearLabel?: string;
15
+ className?: string;
16
+ }
17
+
18
+ const bulkActionBarDefaultActions: PageChromeAction[] = [
19
+ { label: 'Export', icon: Download },
20
+ { label: 'Archive', icon: Archive },
21
+ { label: 'Delete', icon: Trash2, variant: 'destructive' },
22
+ ];
23
+
24
+ /** Contextual toolbar shown while rows are selected: count, bulk actions and clear selection. */
25
+ export function BulkActionBar({
26
+ selectedCount = 3,
27
+ totalCount,
28
+ noun: nounProp,
29
+ actions = bulkActionBarDefaultActions,
30
+ onClear,
31
+ clearLabel: clearLabelProp,
32
+ className,
33
+ }: BulkActionBarProps) {
34
+ const { t, locale } = useI18n();
35
+ const noun = nounProp ?? ([t('bulk.item'), t('bulk.items')] as const);
36
+ const clearLabel = clearLabelProp ?? t('common.clearSelection');
37
+ if (selectedCount <= 0) return null;
38
+ const word = selectedCount === 1 ? noun[0] : noun[1];
39
+ return (
40
+ <Toolbar
41
+ variant="plain"
42
+ aria-label={t('bulk.label')}
43
+ className={cn(
44
+ 'flex-wrap gap-y-2 rounded-lg border border-primary/30 bg-primary/8 px-3 py-2 shadow-sm',
45
+ className,
46
+ )}
47
+ >
48
+ <ToolbarStart>
49
+ <p role="status" className="text-[13px] font-medium text-foreground">
50
+ <span className="font-mono tabular-nums">{selectedCount.toLocaleString(locale)}</span>
51
+ {totalCount != null && (
52
+ <span className="text-muted-foreground"> {t('bulk.of', { total: totalCount.toLocaleString(locale) })}</span>
53
+ )}{' '}
54
+ {word} {t('bulk.selected', { count: selectedCount })}
55
+ </p>
56
+ <ToolbarSeparator />
57
+ <ToolbarButton icon={X} onClick={onClear}>
58
+ {clearLabel}
59
+ </ToolbarButton>
60
+ </ToolbarStart>
61
+ <ToolbarEnd className="flex-wrap">
62
+ {actions.map((a) => (
63
+ <ToolbarButton
64
+ key={a.id ?? a.label}
65
+ variant={a.variant ?? 'secondary'}
66
+ icon={a.icon}
67
+ disabled={a.disabled}
68
+ onClick={a.onClick}
69
+ >
70
+ {a.label}
71
+ </ToolbarButton>
72
+ ))}
73
+ </ToolbarEnd>
74
+ </Toolbar>
75
+ );
76
+ }
@@ -0,0 +1,9 @@
1
+ import type { BlockMeta } from '../../meta';
2
+
3
+ export const meta: BlockMeta = {
4
+ name: 'Bulk action bar',
5
+ family: 'page-chrome',
6
+ status: 'beta',
7
+ description: 'Toolbar that appears while table rows are selected: selection count, bulk actions and clear selection.',
8
+ uses: ['Toolbar', 'ToolbarButton'],
9
+ };
@@ -0,0 +1,68 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import userEvent from '@testing-library/user-event';
3
+ import { createRef } from 'react';
4
+ import { expectNoAxeViolations } from '../../test/a11y';
5
+ import { PageHeader } from './PageHeader';
6
+
7
+ describe('PageHeader', () => {
8
+ it('focuses its title through titleRef without adding a Tab stop', async () => {
9
+ const user = userEvent.setup();
10
+ const titleRef = createRef<HTMLHeadingElement>();
11
+ const { rerender } = render(
12
+ <>
13
+ <PageHeader
14
+ title="Not found"
15
+ titleRef={titleRef}
16
+ as="div"
17
+ breadcrumbs={null}
18
+ description={null}
19
+ status=""
20
+ meta={[]}
21
+ actions={[]}
22
+ tabs={null}
23
+ />
24
+ <button>Go home</button>
25
+ </>,
26
+ );
27
+ const title = screen.getByRole('heading', { level: 1, name: 'Not found' });
28
+ expect(title).toHaveAttribute('tabindex', '-1');
29
+ await user.tab();
30
+ expect(screen.getByRole('button', { name: 'Go home' })).toHaveFocus();
31
+ titleRef.current?.focus();
32
+ expect(title).toHaveFocus();
33
+ rerender(<PageHeader />);
34
+ expect(screen.getByRole('heading', { level: 1 })).not.toHaveAttribute('tabindex');
35
+ });
36
+
37
+ it('renders the default header', async () => {
38
+ const { container } = render(<PageHeader />);
39
+ expect(screen.getByRole('heading', { level: 1, name: 'Deployments' })).toBeInTheDocument();
40
+ expect(screen.getByRole('navigation', { name: 'Breadcrumb' })).toBeInTheDocument();
41
+ expect(screen.getByRole('tablist', { name: 'Page sections' })).toBeInTheDocument();
42
+ expect(screen.getByText('eu-west-1')).toBeInTheDocument();
43
+ await expectNoAxeViolations(container);
44
+ });
45
+
46
+ it('runs actions and reports tab changes', async () => {
47
+ const user = userEvent.setup();
48
+ const onCreate = vi.fn();
49
+ const onTabChange = vi.fn();
50
+ render(
51
+ <PageHeader
52
+ breadcrumbs={null}
53
+ actions={[
54
+ { label: 'Export', variant: 'secondary' },
55
+ { label: 'Create', onClick: onCreate },
56
+ ]}
57
+ onTabChange={onTabChange}
58
+ />,
59
+ );
60
+ // Desktop row + compact row both carry the primary action (CSS picks one).
61
+ const [create] = screen.getAllByRole('button', { name: 'Create' });
62
+ await user.click(create!);
63
+ expect(onCreate).toHaveBeenCalledTimes(1);
64
+ expect(screen.getByRole('button', { name: 'More actions' })).toBeInTheDocument();
65
+ await user.click(screen.getByRole('tab', { name: /Domains/ }));
66
+ expect(onTabChange).toHaveBeenCalledWith('domains');
67
+ });
68
+ });
@@ -0,0 +1,152 @@
1
+ import {
2
+ Breadcrumbs,
3
+ Button,
4
+ MoreMenu,
5
+ StatusTag,
6
+ Tabs,
7
+ TabsList,
8
+ TabsTab,
9
+ cn,
10
+ type BreadcrumbItem,
11
+ type StatusDefinition,
12
+ } from '@gntik-ai/ui';
13
+ import type { LucideIcon } from '@gntik-ai/icons';
14
+ import type { ReactNode, Ref } from 'react';
15
+ import { toMenuAction, type PageChromeAction } from '../types';
16
+ import { pageHeaderActions, pageHeaderBreadcrumbs, pageHeaderMeta, pageHeaderTabs } from './fixtures';
17
+
18
+ export interface PageHeaderMetaItem {
19
+ /** Term (screen-reader only when an icon is shown). */
20
+ label: string;
21
+ value: ReactNode;
22
+ icon?: LucideIcon;
23
+ /** Render the value in Geist Mono (ids, regions, versions). */
24
+ mono?: boolean;
25
+ }
26
+
27
+ export interface PageHeaderTab {
28
+ value: string;
29
+ label: string;
30
+ count?: number;
31
+ }
32
+
33
+ export interface PageHeaderProps {
34
+ /** Root element. Use 'div' where the layout already has a banner (e.g. StackedLayout's pageHeader slot). */
35
+ as?: 'header' | 'div';
36
+ /** Location trail above the title; `null` hides it. */
37
+ breadcrumbs?: BreadcrumbItem[] | null;
38
+ title?: ReactNode;
39
+ /** h1 focus target: adds tabIndex=-1 for titleRef.current.focus(), outside the Tab order. */
40
+ titleRef?: Ref<HTMLHeadingElement>;
41
+ description?: ReactNode;
42
+ /** StatusTag key next to the title (e.g. `active`, `paused`, `failed`). */
43
+ status?: string;
44
+ statuses?: Record<string, StatusDefinition>;
45
+ /** Meta row under the description. */
46
+ meta?: PageHeaderMetaItem[];
47
+ /**
48
+ * Header actions in visual order. The primary one (first with `variant: 'primary'`, else the
49
+ * last) stays visible on small screens; the rest fold into a MoreMenu.
50
+ */
51
+ actions?: PageChromeAction[];
52
+ /** Resource-level navigation tabs under the header; `null` hides them. */
53
+ tabs?: PageHeaderTab[] | null;
54
+ tab?: string;
55
+ defaultTab?: string;
56
+ onTabChange?: (value: string) => void;
57
+ className?: string;
58
+ }
59
+
60
+ /** Screen header: breadcrumb, title + status, description, meta row, actions and optional tabs. */
61
+ export function PageHeader({
62
+ as: Root = 'header',
63
+ breadcrumbs = pageHeaderBreadcrumbs,
64
+ title = 'Deployments',
65
+ titleRef,
66
+ description = 'Every build and release of this project across its environments.',
67
+ status = 'active',
68
+ statuses,
69
+ meta = pageHeaderMeta,
70
+ actions = pageHeaderActions,
71
+ tabs = pageHeaderTabs,
72
+ tab,
73
+ defaultTab,
74
+ onTabChange,
75
+ className,
76
+ }: PageHeaderProps) {
77
+ const explicitPrimary = actions.findIndex((a) => a.variant === 'primary');
78
+ const primaryIndex = explicitPrimary >= 0 ? explicitPrimary : actions.length - 1;
79
+ const primary = actions[primaryIndex];
80
+ const folded = actions.filter((_, i) => i !== primaryIndex);
81
+
82
+ const renderAction = (action: PageChromeAction, index: number) => (
83
+ <Button
84
+ key={action.id ?? action.label}
85
+ variant={action.variant ?? (index === primaryIndex ? 'primary' : 'secondary')}
86
+ icon={action.icon}
87
+ disabled={action.disabled}
88
+ onClick={action.onClick}
89
+ >
90
+ {action.label}
91
+ </Button>
92
+ );
93
+
94
+ return (
95
+ <Root className={cn('min-w-0', className)}>
96
+ {breadcrumbs && breadcrumbs.length > 0 && <Breadcrumbs items={breadcrumbs} maxItems={4} className="mb-2.5" />}
97
+ <div className="flex flex-wrap items-end justify-between gap-4">
98
+ <div className="min-w-0 flex-1">
99
+ <div className="flex flex-wrap items-center gap-x-3 gap-y-1.5">
100
+ <h1
101
+ ref={titleRef}
102
+ tabIndex={titleRef != null ? -1 : undefined}
103
+ className="min-w-0 truncate text-2xl font-bold tracking-tight text-foreground"
104
+ >
105
+ {title}
106
+ </h1>
107
+ {status && <StatusTag status={status} statuses={statuses} />}
108
+ </div>
109
+ {description != null && (
110
+ <p className="mt-1.5 max-w-2xl text-[13px] leading-6 text-muted-foreground">{description}</p>
111
+ )}
112
+ {meta.length > 0 && (
113
+ <dl className="mt-2.5 flex flex-wrap items-center gap-x-4 gap-y-1.5 text-[12.5px] text-muted-foreground">
114
+ {meta.map(({ label, value, icon: Icon, mono }) => (
115
+ <div key={label} className="inline-flex items-center gap-1.5">
116
+ {Icon && <Icon size={14} aria-hidden />}
117
+ <dt className={cn(Icon && 'sr-only')}>{label}</dt>
118
+ <dd className={cn('text-foreground', mono && 'font-mono text-[12px]')}>{value}</dd>
119
+ </div>
120
+ ))}
121
+ </dl>
122
+ )}
123
+ </div>
124
+ {actions.length > 0 && (
125
+ <>
126
+ <div className="hidden shrink-0 items-center gap-2.5 sm:flex">{actions.map(renderAction)}</div>
127
+ <div className="flex shrink-0 items-center gap-2 sm:hidden">
128
+ {folded.length > 0 && <MoreMenu items={folded.map(toMenuAction)} label="More actions" />}
129
+ {primary && renderAction(primary, primaryIndex)}
130
+ </div>
131
+ </>
132
+ )}
133
+ </div>
134
+ {tabs && tabs.length > 0 && (
135
+ <Tabs
136
+ value={tab}
137
+ defaultValue={defaultTab ?? tabs[0]?.value}
138
+ onValueChange={(value) => onTabChange?.(String(value))}
139
+ className="mt-5"
140
+ >
141
+ <TabsList aria-label="Page sections" className="overflow-x-auto">
142
+ {tabs.map((t) => (
143
+ <TabsTab key={t.value} value={t.value} count={t.count}>
144
+ {t.label}
145
+ </TabsTab>
146
+ ))}
147
+ </TabsList>
148
+ </Tabs>
149
+ )}
150
+ </Root>
151
+ );
152
+ }
@@ -0,0 +1,10 @@
1
+ import type { BlockMeta } from '../../meta';
2
+
3
+ export const meta: BlockMeta = {
4
+ name: 'Page header',
5
+ family: 'page-chrome',
6
+ status: 'beta',
7
+ description:
8
+ 'Screen header with breadcrumb, title and status, description, meta row, actions that fold into a MoreMenu on small screens, and optional navigation tabs. titleRef targets the h1 for titleRef.current.focus(), adding tabIndex=-1 only when supplied so the title stays outside the Tab order. In a StatusLayout heading slot use as="div" and disable breadcrumb, meta, tabs, actions, status and description fixtures; the slot supplies the page’s only h1.',
9
+ uses: ['Breadcrumbs', 'Button', 'MoreMenu', 'StatusTag', 'Tabs'],
10
+ };
@@ -0,0 +1,29 @@
1
+ import { Globe, GitBranch, Clock } from '@gntik-ai/icons';
2
+ import type { BreadcrumbItem } from '@gntik-ai/ui';
3
+ import type { PageChromeAction } from '../types';
4
+ import type { PageHeaderMetaItem, PageHeaderTab } from './PageHeader';
5
+
6
+ export const pageHeaderBreadcrumbs: BreadcrumbItem[] = [
7
+ { label: 'Projects', href: '#projects' },
8
+ { label: 'acme-web', href: '#acme-web', mono: true },
9
+ { label: 'Deployments' },
10
+ ];
11
+
12
+ export const pageHeaderMeta: PageHeaderMetaItem[] = [
13
+ { label: 'Region', value: 'eu-west-1', icon: Globe, mono: true },
14
+ { label: 'Branch', value: 'main', icon: GitBranch, mono: true },
15
+ { label: 'Last deploy', value: '12 min ago', icon: Clock },
16
+ ];
17
+
18
+ export const pageHeaderActions: PageChromeAction[] = [
19
+ { label: 'View logs', variant: 'secondary' },
20
+ { label: 'Pause', variant: 'secondary' },
21
+ { label: 'New deployment', variant: 'primary' },
22
+ ];
23
+
24
+ export const pageHeaderTabs: PageHeaderTab[] = [
25
+ { value: 'overview', label: 'Overview' },
26
+ { value: 'deployments', label: 'Deployments', count: 1284 },
27
+ { value: 'domains', label: 'Domains' },
28
+ { value: 'settings', label: 'Settings' },
29
+ ];
@@ -0,0 +1,38 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import userEvent from '@testing-library/user-event';
3
+ import { expectNoAxeViolations } from '../../test/a11y';
4
+ import { PageToolbar } from './PageToolbar';
5
+
6
+ describe('PageToolbar', () => {
7
+ it('renders the default toolbar', async () => {
8
+ const { container } = render(<PageToolbar />);
9
+ expect(screen.getByRole('toolbar', { name: 'Page toolbar' })).toBeInTheDocument();
10
+ expect(screen.getByRole('searchbox', { name: 'Search' })).toBeInTheDocument();
11
+ expect(screen.getByRole('button', { name: 'Table view' })).toHaveAttribute('aria-pressed', 'true');
12
+ await expectNoAxeViolations(container);
13
+ });
14
+
15
+ it('reports search, view and primary action', async () => {
16
+ const user = userEvent.setup();
17
+ const onSearchChange = vi.fn();
18
+ const onViewChange = vi.fn();
19
+ const onCreate = vi.fn();
20
+ render(
21
+ <PageToolbar
22
+ onSearchChange={onSearchChange}
23
+ onViewChange={onViewChange}
24
+ primaryAction={{ label: 'Create', onClick: onCreate }}
25
+ />,
26
+ );
27
+ await user.type(screen.getByRole('searchbox'), 'api');
28
+ expect(onSearchChange).toHaveBeenLastCalledWith('api');
29
+ await user.click(screen.getByRole('button', { name: 'Grid view' }));
30
+ expect(onViewChange).toHaveBeenCalledWith('grid');
31
+ expect(screen.getByRole('button', { name: 'Grid view' })).toHaveAttribute('aria-pressed', 'true');
32
+ // Clicking the active view keeps it selected.
33
+ await user.click(screen.getByRole('button', { name: 'Grid view' }));
34
+ expect(screen.getByRole('button', { name: 'Grid view' })).toHaveAttribute('aria-pressed', 'true');
35
+ await user.click(screen.getByRole('button', { name: 'Create' }));
36
+ expect(onCreate).toHaveBeenCalledTimes(1);
37
+ });
38
+ });
@@ -0,0 +1,138 @@
1
+ import { Plus, Search, SlidersHorizontal } from '@gntik-ai/icons';
2
+ import type { LucideIcon } from '@gntik-ai/icons';
3
+ import {
4
+ Toggle,
5
+ ToggleGroup,
6
+ Toolbar,
7
+ ToolbarButton,
8
+ ToolbarEnd,
9
+ ToolbarInput,
10
+ ToolbarSeparator,
11
+ ToolbarStart,
12
+ cn,
13
+ useI18n,
14
+ } from '@gntik-ai/ui';
15
+ import { useState, type ReactNode } from 'react';
16
+ import type { PageChromeAction } from '../types';
17
+ import { pageToolbarViews } from './fixtures';
18
+
19
+ export interface PageToolbarView {
20
+ value: string;
21
+ /** Accessible name of the icon toggle. */
22
+ label: string;
23
+ icon: LucideIcon;
24
+ }
25
+
26
+ export interface PageToolbarProps {
27
+ /** Controlled search text. */
28
+ search?: string;
29
+ defaultSearch?: string;
30
+ onSearchChange?: (value: string) => void;
31
+ searchLabel?: string;
32
+ searchPlaceholder?: string;
33
+ /**
34
+ * Filter controls. Use ToolbarButton (optionally with `render` for a Menu/Popover trigger) so they
35
+ * join the toolbar's arrow-key navigation. Omitted: a single "Filters" button; `null` hides it.
36
+ */
37
+ filters?: ReactNode;
38
+ /** View modes for the segmented toggle; `null` or an empty list hides it. */
39
+ views?: PageToolbarView[] | null;
40
+ view?: string;
41
+ defaultView?: string;
42
+ onViewChange?: (value: string) => void;
43
+ /** Main action at the end of the bar; `null` hides it. */
44
+ primaryAction?: PageChromeAction | null;
45
+ 'aria-label'?: string;
46
+ className?: string;
47
+ }
48
+
49
+ const pageToolbarDefaultAction: PageChromeAction = { label: 'New project', icon: Plus };
50
+
51
+ /** List/table toolbar: search, filters, view toggle and the primary action, with one Tab stop. */
52
+ export function PageToolbar({
53
+ search,
54
+ defaultSearch = '',
55
+ onSearchChange,
56
+ searchLabel: searchLabelProp,
57
+ searchPlaceholder = 'Search projects…',
58
+ filters,
59
+ views = pageToolbarViews,
60
+ view,
61
+ defaultView,
62
+ onViewChange,
63
+ primaryAction = pageToolbarDefaultAction,
64
+ 'aria-label': ariaLabelProp,
65
+ className,
66
+ }: PageToolbarProps) {
67
+ const { t } = useI18n();
68
+ const searchLabel = searchLabelProp ?? t('common.search');
69
+ const ariaLabel = ariaLabelProp ?? t('toolbar.label');
70
+ const [innerSearch, setInnerSearch] = useState(defaultSearch);
71
+ const [innerView, setInnerView] = useState(defaultView ?? views?.[0]?.value ?? '');
72
+ const currentSearch = search ?? innerSearch;
73
+ const currentView = view ?? innerView;
74
+
75
+ return (
76
+ <Toolbar aria-label={ariaLabel} className={cn('flex-wrap gap-y-2', className)}>
77
+ <ToolbarStart className="min-w-0 flex-1 basis-60">
78
+ <ToolbarInput
79
+ type="search"
80
+ icon={Search}
81
+ aria-label={searchLabel}
82
+ placeholder={searchPlaceholder}
83
+ value={currentSearch}
84
+ onChange={(e) => {
85
+ setInnerSearch(e.target.value);
86
+ onSearchChange?.(e.target.value);
87
+ }}
88
+ wrapperClassName="max-w-sm"
89
+ />
90
+ {filters !== undefined ? (
91
+ filters
92
+ ) : (
93
+ <ToolbarButton variant="secondary" icon={SlidersHorizontal}>
94
+ Filters
95
+ </ToolbarButton>
96
+ )}
97
+ </ToolbarStart>
98
+ <ToolbarEnd>
99
+ {views && views.length > 0 && (
100
+ <>
101
+ <ToggleGroup
102
+ aria-label={t('toolbar.view')}
103
+ size="sm"
104
+ value={[currentView]}
105
+ onValueChange={(next) => {
106
+ const picked = next[0];
107
+ if (!picked) return; // keep one view selected
108
+ setInnerView(picked);
109
+ onViewChange?.(picked);
110
+ }}
111
+ >
112
+ {views.map((v) => (
113
+ <ToolbarButton
114
+ key={v.value}
115
+ render={<Toggle value={v.value} iconOnly size="sm" />}
116
+ aria-label={v.label}
117
+ icon={v.icon}
118
+ className="size-7 rounded-md data-pressed:bg-secondary data-pressed:text-foreground"
119
+ />
120
+ ))}
121
+ </ToggleGroup>
122
+ {primaryAction && <ToolbarSeparator />}
123
+ </>
124
+ )}
125
+ {primaryAction && (
126
+ <ToolbarButton
127
+ variant={primaryAction.variant ?? 'primary'}
128
+ icon={primaryAction.icon}
129
+ disabled={primaryAction.disabled}
130
+ onClick={primaryAction.onClick}
131
+ >
132
+ {primaryAction.label}
133
+ </ToolbarButton>
134
+ )}
135
+ </ToolbarEnd>
136
+ </Toolbar>
137
+ );
138
+ }
@@ -0,0 +1,10 @@
1
+ import type { BlockMeta } from '../../meta';
2
+
3
+ export const meta: BlockMeta = {
4
+ name: 'Page toolbar',
5
+ family: 'page-chrome',
6
+ status: 'beta',
7
+ description:
8
+ 'Toolbar above a list or table: search, a filters slot, a view toggle and the primary action, with roving focus.',
9
+ uses: ['Toolbar', 'ToolbarInput', 'ToolbarButton', 'ToggleGroup', 'Toggle'],
10
+ };
@@ -0,0 +1,8 @@
1
+ import { LayoutGrid, List, Table2 } from '@gntik-ai/icons';
2
+ import type { PageToolbarView } from './PageToolbar';
3
+
4
+ export const pageToolbarViews: PageToolbarView[] = [
5
+ { value: 'table', label: 'Table view', icon: Table2 },
6
+ { value: 'list', label: 'List view', icon: List },
7
+ { value: 'grid', label: 'Grid view', icon: LayoutGrid },
8
+ ];
@@ -0,0 +1,28 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import userEvent from '@testing-library/user-event';
3
+ import { expectNoAxeViolations } from '../../test/a11y';
4
+ import { SectionHeader } from './SectionHeader';
5
+
6
+ describe('SectionHeader', () => {
7
+ it('renders the default header', async () => {
8
+ const { container } = render(<SectionHeader />);
9
+ expect(screen.getByRole('heading', { level: 2, name: 'Policies' })).toBeInTheDocument();
10
+ await expectNoAxeViolations(container);
11
+ });
12
+
13
+ it('shows a count and runs its actions', async () => {
14
+ const onInvite = vi.fn();
15
+ render(
16
+ <SectionHeader
17
+ title="Members"
18
+ as="h3"
19
+ count={8}
20
+ actions={[{ label: 'Invite', variant: 'soft', onClick: onInvite }]}
21
+ />,
22
+ );
23
+ expect(screen.getByRole('heading', { level: 3, name: 'Members' })).toBeInTheDocument();
24
+ expect(screen.getByText('8')).toBeInTheDocument();
25
+ await userEvent.setup().click(screen.getByRole('button', { name: 'Invite' }));
26
+ expect(onInvite).toHaveBeenCalledTimes(1);
27
+ });
28
+ });