@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,226 @@
1
+ import { CircleHelp } from '@gntik-ai/icons';
2
+ import {
3
+ Link,
4
+ Logo,
5
+ Meter,
6
+ NavList,
7
+ UserMenu,
8
+ WorkspaceSwitcher,
9
+ cn,
10
+ type NavGroup,
11
+ type NavItem,
12
+ type UserMenuEntry,
13
+ type UserMenuUser,
14
+ type Workspace,
15
+ } from '@gntik-ai/ui';
16
+ import { useState } from 'react';
17
+ import {
18
+ appSidebarNavGroups,
19
+ appSidebarUsage,
20
+ appSidebarUser,
21
+ appSidebarWorkspaces,
22
+ type AppSidebarUsage,
23
+ } from './fixtures';
24
+
25
+ export interface AppSidebarHeaderProps {
26
+ /** Icon rail: shows only the brand mark. */
27
+ collapsed?: boolean;
28
+ /** Workspaces for the switcher; `null` shows the logo + wordmark instead. */
29
+ workspaces?: Workspace[] | null;
30
+ currentWorkspaceId?: string;
31
+ onWorkspaceChange?: (workspace: Workspace) => void;
32
+ onCreateWorkspace?: () => void;
33
+ className?: string;
34
+ }
35
+
36
+ /** Sidebar top: workspace switcher (or the brand logo), the brand mark alone in the icon rail. */
37
+ export function AppSidebarHeader({
38
+ collapsed = false,
39
+ workspaces = appSidebarWorkspaces,
40
+ currentWorkspaceId,
41
+ onWorkspaceChange,
42
+ onCreateWorkspace,
43
+ className,
44
+ }: AppSidebarHeaderProps) {
45
+ const [current, setCurrent] = useState(currentWorkspaceId ?? workspaces?.[0]?.id ?? '');
46
+ const currentId = currentWorkspaceId ?? current;
47
+ if (collapsed) return <Logo size={26} className={className} />;
48
+ if (!workspaces || workspaces.length === 0) return <Logo size={24} wordmark className={cn('px-1', className)} />;
49
+ return (
50
+ <WorkspaceSwitcher
51
+ className={cn('w-full', className)}
52
+ workspaces={workspaces}
53
+ currentId={currentId}
54
+ onCreate={onCreateWorkspace}
55
+ onSelect={(w) => {
56
+ setCurrent(w.id);
57
+ onWorkspaceChange?.(w);
58
+ }}
59
+ />
60
+ );
61
+ }
62
+
63
+ export interface AppSidebarNavProps {
64
+ groups?: NavGroup[];
65
+ currentHref?: string;
66
+ collapsed?: boolean;
67
+ /** Accessible name of the nav landmark. */
68
+ label?: string;
69
+ onNavigate?: (item: NavItem) => void;
70
+ className?: string;
71
+ }
72
+
73
+ /** Sidebar body: the NavList (icon rail with tooltips when collapsed). */
74
+ export function AppSidebarNav({
75
+ groups = appSidebarNavGroups,
76
+ currentHref = '/projects',
77
+ collapsed = false,
78
+ label = 'Main',
79
+ onNavigate,
80
+ className,
81
+ }: AppSidebarNavProps) {
82
+ return (
83
+ <NavList
84
+ groups={groups}
85
+ currentHref={currentHref}
86
+ collapsed={collapsed}
87
+ label={label}
88
+ onNavigate={onNavigate}
89
+ className={className}
90
+ />
91
+ );
92
+ }
93
+
94
+ export interface AppSidebarFooterProps {
95
+ collapsed?: boolean;
96
+ /** Plan usage meter; `null` hides it. */
97
+ usage?: AppSidebarUsage | null;
98
+ /** Help link; `null` hides it. */
99
+ helpHref?: string | null;
100
+ helpLabel?: string;
101
+ /** Signed-in user; `null` hides the user menu. */
102
+ user?: UserMenuUser | null;
103
+ userMenuItems?: UserMenuEntry[];
104
+ onSignOut?: () => void;
105
+ className?: string;
106
+ }
107
+
108
+ /** Sidebar bottom: usage meter, help link and the user menu (avatar only in the icon rail). */
109
+ export function AppSidebarFooter({
110
+ collapsed = false,
111
+ usage = appSidebarUsage,
112
+ helpHref = '#help',
113
+ helpLabel = 'Help & docs',
114
+ user = appSidebarUser,
115
+ userMenuItems,
116
+ onSignOut,
117
+ className,
118
+ }: AppSidebarFooterProps) {
119
+ return (
120
+ <div className={cn('flex flex-col gap-1.5', collapsed && 'items-center', className)}>
121
+ {usage && !collapsed && (
122
+ <div className="rounded-[10px] border border-border bg-card p-2.5">
123
+ <Meter
124
+ size="sm"
125
+ label={usage.label}
126
+ value={usage.value}
127
+ max={usage.max}
128
+ valueLabel={usage.valueLabel}
129
+ aria-valuetext={usage.valueLabel}
130
+ />
131
+ </div>
132
+ )}
133
+ {helpHref && (
134
+ <Link
135
+ href={helpHref}
136
+ tone="muted"
137
+ underline="none"
138
+ aria-label={collapsed ? helpLabel : undefined}
139
+ className={cn(
140
+ 'flex h-[34px] items-center gap-2.5 rounded-lg px-2.5 text-[13px] font-medium hover:bg-accent/55 hover:text-foreground',
141
+ collapsed && 'size-9 justify-center px-0',
142
+ )}
143
+ >
144
+ <CircleHelp size={16} aria-hidden />
145
+ {!collapsed && helpLabel}
146
+ </Link>
147
+ )}
148
+ {user && (
149
+ <UserMenu
150
+ user={user}
151
+ items={userMenuItems}
152
+ onSignOut={onSignOut}
153
+ showName={!collapsed}
154
+ className={collapsed ? undefined : 'w-full justify-start'}
155
+ />
156
+ )}
157
+ </div>
158
+ );
159
+ }
160
+
161
+ export interface AppSidebarProps
162
+ extends
163
+ AppSidebarHeaderProps,
164
+ Omit<AppSidebarNavProps, 'className' | 'groups' | 'label'>,
165
+ Omit<AppSidebarFooterProps, 'className'> {
166
+ navGroups?: NavGroup[];
167
+ navLabel?: string;
168
+ }
169
+
170
+ /**
171
+ * Complete application sidebar column (chrome surface): workspace switcher header, NavList body
172
+ * and a footer with usage, help and the user menu. Inside SidebarLayout, use the parts
173
+ * (AppSidebarHeader / AppSidebarNav / AppSidebarFooter) in its three slots.
174
+ */
175
+ export function AppSidebar({
176
+ collapsed = false,
177
+ workspaces,
178
+ currentWorkspaceId,
179
+ onWorkspaceChange,
180
+ onCreateWorkspace,
181
+ navGroups,
182
+ navLabel,
183
+ currentHref,
184
+ onNavigate,
185
+ usage,
186
+ helpHref,
187
+ helpLabel,
188
+ user,
189
+ userMenuItems,
190
+ onSignOut,
191
+ className,
192
+ }: AppSidebarProps) {
193
+ return (
194
+ <div className={cn('flex h-full min-h-0 flex-col bg-chrome', collapsed ? 'w-16' : 'w-[248px]', className)}>
195
+ <div className={cn('flex shrink-0 items-center pt-4 pb-3', collapsed ? 'justify-center px-2' : 'px-3')}>
196
+ <AppSidebarHeader
197
+ collapsed={collapsed}
198
+ workspaces={workspaces}
199
+ currentWorkspaceId={currentWorkspaceId}
200
+ onWorkspaceChange={onWorkspaceChange}
201
+ onCreateWorkspace={onCreateWorkspace}
202
+ />
203
+ </div>
204
+ <div className={cn('min-h-0 flex-1 overflow-y-auto pt-1 pb-3', collapsed ? 'px-2' : 'px-3')}>
205
+ <AppSidebarNav
206
+ groups={navGroups}
207
+ label={navLabel}
208
+ currentHref={currentHref}
209
+ collapsed={collapsed}
210
+ onNavigate={onNavigate}
211
+ />
212
+ </div>
213
+ <div className={cn('shrink-0 border-t border-border/60 p-2.5', collapsed && 'px-2')}>
214
+ <AppSidebarFooter
215
+ collapsed={collapsed}
216
+ usage={usage}
217
+ helpHref={helpHref}
218
+ helpLabel={helpLabel}
219
+ user={user}
220
+ userMenuItems={userMenuItems}
221
+ onSignOut={onSignOut}
222
+ />
223
+ </div>
224
+ </div>
225
+ );
226
+ }
@@ -0,0 +1,10 @@
1
+ import type { BlockMeta } from '../../meta';
2
+
3
+ export const meta: BlockMeta = {
4
+ name: 'App sidebar',
5
+ family: 'shell',
6
+ status: 'beta',
7
+ description:
8
+ 'Application sidebar: workspace switcher (or logo) header, NavList body with icon-rail support, and a footer with plan usage, help link and user menu. Exposes header / nav / footer parts for the SidebarLayout slots.',
9
+ uses: ['WorkspaceSwitcher', 'Logo', 'NavList', 'Meter', 'Link', 'UserMenu'],
10
+ };
@@ -0,0 +1,58 @@
1
+ import {
2
+ Activity,
3
+ BarChart3,
4
+ CreditCard,
5
+ FolderKanban,
6
+ Home,
7
+ KeyRound,
8
+ Rocket,
9
+ Settings,
10
+ Users,
11
+ } from '@gntik-ai/icons';
12
+ import type { NavGroup, UserMenuUser, Workspace } from '@gntik-ai/ui';
13
+
14
+ export const appSidebarNavGroups: NavGroup[] = [
15
+ {
16
+ label: 'Workspace',
17
+ items: [
18
+ { label: 'Overview', href: '/overview', icon: Home },
19
+ { label: 'Projects', href: '/projects', icon: FolderKanban, badge: 12 },
20
+ { label: 'Deployments', href: '/deployments', icon: Rocket, badge: 3 },
21
+ { label: 'Analytics', href: '/analytics', icon: BarChart3 },
22
+ ],
23
+ },
24
+ {
25
+ label: 'Admin',
26
+ collapsible: true,
27
+ items: [
28
+ { label: 'Activity', href: '/activity', icon: Activity },
29
+ { label: 'Members', href: '/members', icon: Users },
30
+ { label: 'Billing', href: '/billing', icon: CreditCard },
31
+ { label: 'API keys', href: '/api-keys', icon: KeyRound },
32
+ { label: 'Settings', href: '/settings', icon: Settings },
33
+ ],
34
+ },
35
+ ];
36
+
37
+ export const appSidebarWorkspaces: Workspace[] = [
38
+ { id: 'acme', name: 'Acme Industries', meta: 'Enterprise · Owner' },
39
+ { id: 'northbeam', name: 'Northbeam Labs', meta: 'Pro · Admin' },
40
+ { id: 'personal', name: 'Personal', meta: 'Free · Owner' },
41
+ ];
42
+
43
+ export interface AppSidebarUsage {
44
+ label: string;
45
+ value: number;
46
+ max: number;
47
+ /** Formatted value, e.g. "8.2k / 10k requests". */
48
+ valueLabel?: string;
49
+ }
50
+
51
+ export const appSidebarUsage: AppSidebarUsage = {
52
+ label: 'Monthly requests',
53
+ value: 8200,
54
+ max: 10000,
55
+ valueLabel: '8.2k / 10k',
56
+ };
57
+
58
+ export const appSidebarUser: UserMenuUser = { name: 'Dana Whitfield', email: 'dana@example.com' };
@@ -0,0 +1,41 @@
1
+ import { ThemeProvider } from '@gntik-ai/ui';
2
+ import { render, screen, within } from '@testing-library/react';
3
+ import userEvent from '@testing-library/user-event';
4
+ import { expectNoAxeViolations } from '../../test/a11y';
5
+ import { AppTopbar } from './AppTopbar';
6
+
7
+ describe('AppTopbar', () => {
8
+ it('renders breadcrumb, environment, search, notifications, theme and user', async () => {
9
+ const { container } = render(
10
+ <ThemeProvider storageKey={null}>
11
+ <AppTopbar />
12
+ </ThemeProvider>,
13
+ );
14
+ const crumbs = screen.getByRole('navigation', { name: /breadcrumb/i });
15
+ expect(within(crumbs).getByText('web-frontend')).toBeInTheDocument();
16
+ expect(container.querySelector('[data-environment="production"]')).not.toBeNull();
17
+ expect(screen.getByRole('button', { name: /Search/ })).toBeInTheDocument();
18
+ expect(screen.getByRole('button', { name: /notifications/i })).toBeInTheDocument();
19
+ await expectNoAxeViolations(container);
20
+ }, 15_000);
21
+
22
+ it('opens the notifications and selects one', async () => {
23
+ const user = userEvent.setup();
24
+ const onNotificationSelect = vi.fn();
25
+ render(
26
+ <ThemeProvider storageKey={null}>
27
+ <AppTopbar onNotificationSelect={onNotificationSelect} search={null} theme="segmented" />
28
+ </ThemeProvider>,
29
+ );
30
+ await user.click(screen.getByRole('button', { name: /notifications/i }));
31
+ await user.click(await screen.findByText('api-gateway reached 97% of its monthly budget'));
32
+ expect(onNotificationSelect).toHaveBeenCalledWith(expect.objectContaining({ id: 'n1' }));
33
+ });
34
+
35
+ it('hides optional parts with null', () => {
36
+ const { container } = render(
37
+ <AppTopbar breadcrumbs={null} environment={null} search={null} notifications={null} theme={null} user={null} />,
38
+ );
39
+ expect(container.querySelectorAll('button')).toHaveLength(0);
40
+ });
41
+ });
@@ -0,0 +1,82 @@
1
+ import {
2
+ Breadcrumbs,
3
+ NotificationsPopover,
4
+ ThemeCycleButton,
5
+ ThemeSwitcher,
6
+ UserMenu,
7
+ cn,
8
+ type BreadcrumbItem,
9
+ type NotificationItem,
10
+ type UserMenuEntry,
11
+ type UserMenuUser,
12
+ } from '@gntik-ai/ui';
13
+ import type { ReactNode } from 'react';
14
+ import { appSidebarUser } from '../AppSidebar/fixtures';
15
+ import { EnvironmentBadge, type EnvironmentBadgeProps } from '../EnvironmentBadge/EnvironmentBadge';
16
+ import { GlobalSearch } from '../GlobalSearch/GlobalSearch';
17
+ import { appTopbarBreadcrumbs, appTopbarNotifications } from './fixtures';
18
+
19
+ export interface AppTopbarProps {
20
+ /** Location trail; `null` hides it. */
21
+ breadcrumbs?: BreadcrumbItem[] | null;
22
+ /** Replaces the breadcrumb (custom title, tabs…). */
23
+ breadcrumb?: ReactNode;
24
+ /** Environment chip after the breadcrumb; `null` hides it. */
25
+ environment?: EnvironmentBadgeProps | null;
26
+ /** Search control; defaults to GlobalSearch (⌘K). `null` hides it. */
27
+ search?: ReactNode;
28
+ /** Notifications; `null` hides the bell. */
29
+ notifications?: NotificationItem[] | null;
30
+ onNotificationSelect?: (notification: NotificationItem) => void;
31
+ onMarkAllRead?: () => void;
32
+ /** Theme control: a one-button cycle, the segmented switcher, or none. */
33
+ theme?: 'cycle' | 'segmented' | null;
34
+ /** Signed-in user; `null` hides the user menu. */
35
+ user?: UserMenuUser | null;
36
+ userMenuItems?: UserMenuEntry[];
37
+ onSignOut?: () => void;
38
+ className?: string;
39
+ }
40
+
41
+ /**
42
+ * Topbar contents: breadcrumb (+ environment chip) on the left; ⌘K search, notifications, theme
43
+ * and user menu on the right. Renders no <header>: put it in SidebarLayout's `topbar` slot or a
44
+ * header of your own.
45
+ */
46
+ export function AppTopbar({
47
+ breadcrumbs = appTopbarBreadcrumbs,
48
+ breadcrumb,
49
+ environment = {},
50
+ search,
51
+ notifications = appTopbarNotifications,
52
+ onNotificationSelect,
53
+ onMarkAllRead,
54
+ theme = 'cycle',
55
+ user = appSidebarUser,
56
+ userMenuItems,
57
+ onSignOut,
58
+ className,
59
+ }: AppTopbarProps) {
60
+ const searchNode = search === undefined ? <GlobalSearch className="hidden md:inline-flex" /> : search;
61
+ return (
62
+ <div className={cn('flex min-w-0 flex-1 items-center gap-2 lg:gap-3', className)}>
63
+ <div className="flex min-w-0 flex-1 items-center gap-2.5">
64
+ {breadcrumb ?? (breadcrumbs && <Breadcrumbs className="min-w-0" items={breadcrumbs} />)}
65
+ {environment && (
66
+ <EnvironmentBadge {...environment} className={cn('hidden sm:inline-flex', environment.className)} />
67
+ )}
68
+ </div>
69
+ {searchNode}
70
+ {notifications && (
71
+ <NotificationsPopover
72
+ notifications={notifications}
73
+ onSelect={onNotificationSelect}
74
+ onMarkAllRead={onMarkAllRead}
75
+ />
76
+ )}
77
+ {theme === 'cycle' && <ThemeCycleButton />}
78
+ {theme === 'segmented' && <ThemeSwitcher size="sm" className="hidden md:inline-flex" />}
79
+ {user && <UserMenu user={user} items={userMenuItems} onSignOut={onSignOut} />}
80
+ </div>
81
+ );
82
+ }
@@ -0,0 +1,19 @@
1
+ import type { BlockMeta } from '../../meta';
2
+
3
+ export const meta: BlockMeta = {
4
+ name: 'App topbar',
5
+ family: 'shell',
6
+ status: 'beta',
7
+ description:
8
+ 'Topbar contents for the application shell: breadcrumb and environment chip, ⌘K global search, notifications popover, theme control and user menu.',
9
+ uses: [
10
+ 'Breadcrumbs',
11
+ 'Badge',
12
+ 'CommandPalette',
13
+ 'CommandPaletteTrigger',
14
+ 'NotificationsPopover',
15
+ 'ThemeCycleButton',
16
+ 'ThemeSwitcher',
17
+ 'UserMenu',
18
+ ],
19
+ };
@@ -0,0 +1,22 @@
1
+ import type { BreadcrumbItem, NotificationItem } from '@gntik-ai/ui';
2
+
3
+ export const appTopbarBreadcrumbs: BreadcrumbItem[] = [
4
+ { label: 'Acme Industries', href: '/overview' },
5
+ { label: 'Projects', href: '/projects' },
6
+ { label: 'web-frontend', mono: true },
7
+ ];
8
+
9
+ const MIN = 60_000;
10
+ const NOW = Date.now();
11
+
12
+ export const appTopbarNotifications: NotificationItem[] = [
13
+ {
14
+ id: 'n1',
15
+ tone: 'warning',
16
+ title: 'api-gateway reached 97% of its monthly budget',
17
+ body: 'Requests are throttled until the limit is raised.',
18
+ time: NOW - 2 * MIN,
19
+ },
20
+ { id: 'n2', tone: 'primary', title: 'Deployment web-frontend (build 418) succeeded', time: NOW - 18 * MIN },
21
+ { id: 'n3', tone: 'info', title: 'Sam Patel invited you to Data Platform', time: NOW - 65 * MIN, read: true },
22
+ ];
@@ -0,0 +1,22 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import { expectNoAxeViolations } from '../../test/a11y';
3
+ import { EnvironmentBadge } from './EnvironmentBadge';
4
+
5
+ describe('EnvironmentBadge', () => {
6
+ it('renders the default environment and region', async () => {
7
+ const { container } = render(<EnvironmentBadge />);
8
+ const badge = container.querySelector('[data-environment]');
9
+ expect(badge).toHaveAttribute('data-environment', 'production');
10
+ expect(badge).toHaveTextContent(/production.*eu-west/);
11
+ expect(screen.getByText('Environment:')).toHaveClass('sr-only');
12
+ await expectNoAxeViolations(container);
13
+ });
14
+
15
+ it('picks the tone from the environment and accepts an override', () => {
16
+ const { container, rerender } = render(<EnvironmentBadge environment="staging" region="us-east" />);
17
+ expect(container.textContent).toContain('staging');
18
+ rerender(<EnvironmentBadge environment="sandbox" region={null} tone="info" />);
19
+ expect(container.textContent).not.toContain('·');
20
+ expect(container.textContent).toContain('sandbox');
21
+ });
22
+ });
@@ -0,0 +1,55 @@
1
+ import { Badge, cn } from '@gntik-ai/ui';
2
+
3
+ export type EnvironmentTone = 'primary' | 'warning' | 'info' | 'neutral' | 'destructive';
4
+
5
+ /** Default tone per environment name; anything else is neutral. */
6
+ export const environmentTones: Record<string, EnvironmentTone> = {
7
+ production: 'primary',
8
+ prod: 'primary',
9
+ staging: 'warning',
10
+ preview: 'info',
11
+ development: 'neutral',
12
+ dev: 'neutral',
13
+ };
14
+
15
+ export interface EnvironmentBadgeProps {
16
+ /** Environment name, e.g. `production`, `staging`, `preview`. */
17
+ environment?: string;
18
+ /** Region or cluster shown after the environment, e.g. `eu-west`; `null` hides it. */
19
+ region?: string | null;
20
+ /** Overrides the tone looked up from `environmentTones`. */
21
+ tone?: EnvironmentTone;
22
+ /** Prefix read by screen readers only. */
23
+ srLabel?: string;
24
+ className?: string;
25
+ }
26
+
27
+ /** Environment + region chip for the topbar or a page header: "production · eu-west". */
28
+ export function EnvironmentBadge({
29
+ environment = 'production',
30
+ region = 'eu-west',
31
+ tone,
32
+ srLabel = 'Environment:',
33
+ className,
34
+ }: EnvironmentBadgeProps) {
35
+ const resolved = tone ?? environmentTones[environment.toLowerCase()] ?? 'neutral';
36
+ return (
37
+ <Badge
38
+ tone={resolved}
39
+ dot
40
+ data-environment={environment}
41
+ className={cn('font-mono text-[10.5px] whitespace-nowrap', className)}
42
+ >
43
+ <span className="sr-only">{srLabel} </span>
44
+ {environment}
45
+ {region && (
46
+ <>
47
+ <span aria-hidden className="opacity-60">
48
+ ·
49
+ </span>
50
+ <span className="sr-only">, region</span> {region}
51
+ </>
52
+ )}
53
+ </Badge>
54
+ );
55
+ }
@@ -0,0 +1,10 @@
1
+ import type { BlockMeta } from '../../meta';
2
+
3
+ export const meta: BlockMeta = {
4
+ name: 'Environment badge',
5
+ family: 'shell',
6
+ status: 'beta',
7
+ description:
8
+ 'Environment + region chip ("production · eu-west") for the topbar or a page header; tone follows the environment.',
9
+ uses: ['Badge'],
10
+ };
@@ -0,0 +1,28 @@
1
+ import { render, screen, waitFor, within } from '@testing-library/react';
2
+ import userEvent from '@testing-library/user-event';
3
+ import { expectNoAxeViolations } from '../../test/a11y';
4
+ import { GlobalSearch } from './GlobalSearch';
5
+
6
+ describe('GlobalSearch', () => {
7
+ it('renders the trigger with the shortcut hint', async () => {
8
+ const { container } = render(<GlobalSearch />);
9
+ expect(screen.getByRole('button', { name: /Search/ })).toBeInTheDocument();
10
+ await expectNoAxeViolations(container);
11
+ });
12
+
13
+ it('⌘K opens it with recent and grouped results; choosing one reports its group', async () => {
14
+ const user = userEvent.setup();
15
+ const onSelect = vi.fn();
16
+ render(<GlobalSearch onSelect={onSelect} />);
17
+ await user.keyboard('{Meta>}k{/Meta}');
18
+ const dialog = await screen.findByRole('dialog', { name: 'Global search' });
19
+ for (const heading of ['Recent', 'Pages', 'Records', 'Actions'])
20
+ expect(within(dialog).getByText(heading)).toBeInTheDocument();
21
+ await expectNoAxeViolations();
22
+ await user.keyboard('INV-2041');
23
+ await user.keyboard('{Enter}');
24
+ await waitFor(() => expect(onSelect).toHaveBeenCalledTimes(1));
25
+ expect(onSelect.mock.calls[0]?.[0]).toMatchObject({ id: 'record-inv-2041' });
26
+ expect(onSelect.mock.calls[0]?.[1]).toBe('records');
27
+ }, 15_000);
28
+ });
@@ -0,0 +1,80 @@
1
+ import { CommandPalette, CommandPaletteTrigger, type CommandGroup, type CommandItem, useI18n } from '@gntik-ai/ui';
2
+ import { globalSearchActions, globalSearchPages, globalSearchRecent, globalSearchRecords } from './fixtures';
3
+
4
+ export type GlobalSearchGroup = 'pages' | 'records' | 'actions' | 'recent';
5
+
6
+ export interface GlobalSearchProps {
7
+ pages?: CommandItem[];
8
+ records?: CommandItem[];
9
+ actions?: CommandItem[];
10
+ /** Shown first while the query is empty. */
11
+ recent?: CommandItem[];
12
+ /** Called with every chosen item and the group it came from (after the item's own onSelect). */
13
+ onSelect?: (item: CommandItem, group: GlobalSearchGroup) => void;
14
+ /** Group headings. */
15
+ labels?: Partial<Record<GlobalSearchGroup, string>>;
16
+ /** Registers ⌘K / Ctrl+K. */
17
+ shortcut?: boolean;
18
+ open?: boolean;
19
+ defaultOpen?: boolean;
20
+ onOpenChange?: (open: boolean) => void;
21
+ placeholder?: string;
22
+ /** Text of the trigger button. */
23
+ triggerLabel?: string;
24
+ /** Classes for the trigger (e.g. `hidden md:inline-flex`). */
25
+ className?: string;
26
+ }
27
+
28
+ /** ⌘K search: a CommandPalette pre-wired with recent items and Pages / Records / Actions groups. */
29
+ export function GlobalSearch({
30
+ pages = globalSearchPages,
31
+ records = globalSearchRecords,
32
+ actions = globalSearchActions,
33
+ recent = globalSearchRecent,
34
+ onSelect,
35
+ labels,
36
+ shortcut = true,
37
+ open,
38
+ defaultOpen,
39
+ onOpenChange,
40
+ placeholder: placeholderProp,
41
+ triggerLabel: triggerLabelProp,
42
+ className,
43
+ }: GlobalSearchProps) {
44
+ const { t } = useI18n();
45
+ const placeholder = placeholderProp ?? t('globalSearch.placeholder');
46
+ const triggerLabel = triggerLabelProp ?? t('common.search');
47
+ const l: Record<GlobalSearchGroup, string> = {
48
+ pages: labels?.pages ?? t('globalSearch.pages'),
49
+ records: labels?.records ?? t('globalSearch.records'),
50
+ actions: labels?.actions ?? t('globalSearch.actions'),
51
+ recent: labels?.recent ?? t('commandPalette.recent'),
52
+ };
53
+ const origin = new Map<string, GlobalSearchGroup>();
54
+ const tag = (items: CommandItem[], group: GlobalSearchGroup) => {
55
+ for (const item of items) if (!origin.has(item.id)) origin.set(item.id, group);
56
+ return items;
57
+ };
58
+ const groups: CommandGroup[] = [
59
+ { label: l.pages, items: tag(pages, 'pages') },
60
+ { label: l.records, items: tag(records, 'records') },
61
+ { label: l.actions, items: tag(actions, 'actions') },
62
+ ].filter((g) => g.items.length > 0);
63
+ for (const item of recent) if (!origin.has(item.id)) origin.set(item.id, 'recent');
64
+ return (
65
+ <CommandPalette
66
+ groups={groups}
67
+ recent={recent}
68
+ recentLabel={l.recent}
69
+ shortcut={shortcut}
70
+ open={open}
71
+ defaultOpen={defaultOpen}
72
+ onOpenChange={onOpenChange}
73
+ placeholder={placeholder}
74
+ label={t('globalSearch.label')}
75
+ onSelect={(item) => onSelect?.(item, origin.get(item.id) ?? 'recent')}
76
+ >
77
+ <CommandPaletteTrigger className={className}>{triggerLabel}</CommandPaletteTrigger>
78
+ </CommandPalette>
79
+ );
80
+ }
@@ -0,0 +1,10 @@
1
+ import type { BlockMeta } from '../../meta';
2
+
3
+ export const meta: BlockMeta = {
4
+ name: 'Global search',
5
+ family: 'shell',
6
+ status: 'beta',
7
+ description:
8
+ '⌘K search button opening a CommandPalette pre-wired with recent items and grouped results: pages, records and actions.',
9
+ uses: ['CommandPalette', 'CommandPaletteTrigger', 'Kbd'],
10
+ };