@devxcrew/react-ui 0.0.0-stage → 0.1.7

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 (326) hide show
  1. package/README.md +85 -2
  2. package/package.json +105 -4
  3. package/src/blocks/agent-chat-workspace/agent-chat-workspace.tsx +162 -0
  4. package/src/blocks/agent-chat-workspace/index.ts +2 -0
  5. package/src/blocks/agent-provider-settings/agent-provider-settings.tsx +183 -0
  6. package/src/blocks/agent-provider-settings/index.ts +2 -0
  7. package/src/blocks/agent-task-workspace/agent-task-workspace.tsx +48 -0
  8. package/src/blocks/agent-task-workspace/index.ts +2 -0
  9. package/src/blocks/analysis-context/analysis-context.tsx +6 -0
  10. package/src/blocks/analysis-context/index.ts +1 -0
  11. package/src/blocks/app-header/app-header.tsx +159 -0
  12. package/src/blocks/app-header/index.ts +2 -0
  13. package/src/blocks/archived-chat-workspace/archived-chat-workspace.tsx +29 -0
  14. package/src/blocks/archived-chat-workspace/index.ts +2 -0
  15. package/src/blocks/auth/README.md +32 -0
  16. package/src/blocks/auth/admin-login-page.tsx +15 -0
  17. package/src/blocks/auth/admin-password-forgot-page.tsx +5 -0
  18. package/src/blocks/auth/auth-page-layout.tsx +90 -0
  19. package/src/blocks/auth/client-login-page.tsx +20 -0
  20. package/src/blocks/auth/client-password-forgot-page.tsx +5 -0
  21. package/src/blocks/auth/identity-management-desk.tsx +530 -0
  22. package/src/blocks/auth/index.ts +12 -0
  23. package/src/blocks/auth/login-page.tsx +206 -0
  24. package/src/blocks/auth/password-forgot-page.tsx +60 -0
  25. package/src/blocks/auth/portal-pages.tsx +32 -0
  26. package/src/blocks/auth/privileged-desk.tsx +55 -0
  27. package/src/blocks/auth/register-page.tsx +117 -0
  28. package/src/blocks/auth/session-boundary.tsx +296 -0
  29. package/src/blocks/auth/super-admin-login-page.tsx +15 -0
  30. package/src/blocks/auth/super-admin-password-forgot-page.tsx +5 -0
  31. package/src/blocks/blog/blog-card.tsx +156 -0
  32. package/src/blocks/blog/blog-reader.tsx +175 -0
  33. package/src/blocks/blog/blog-types.ts +47 -0
  34. package/src/blocks/blog/index.ts +3 -0
  35. package/src/blocks/chat-composer/chat-composer.tsx +140 -0
  36. package/src/blocks/chat-composer/index.ts +1 -0
  37. package/src/blocks/chat-history/chat-history.tsx +59 -0
  38. package/src/blocks/chat-history/index.ts +1 -0
  39. package/src/blocks/chat-response-progress/chat-response-progress.tsx +34 -0
  40. package/src/blocks/chat-response-progress/index.ts +2 -0
  41. package/src/blocks/chat-runtime-controls/chat-runtime-controls.tsx +56 -0
  42. package/src/blocks/chat-runtime-controls/index.ts +2 -0
  43. package/src/blocks/chat-runtime-trace/chat-runtime-trace.tsx +71 -0
  44. package/src/blocks/chat-runtime-trace/index.ts +2 -0
  45. package/src/blocks/codex-connection-settings/codex-connection-settings.tsx +42 -0
  46. package/src/blocks/codex-connection-settings/index.ts +1 -0
  47. package/src/blocks/dropzone/dropzone-file-item.tsx +113 -0
  48. package/src/blocks/dropzone/dropzone-types.ts +32 -0
  49. package/src/blocks/dropzone/dropzone.tsx +216 -0
  50. package/src/blocks/dropzone/index.ts +3 -0
  51. package/src/blocks/ecommerce/cart/cart-drawer.tsx +228 -0
  52. package/src/blocks/ecommerce/cart/cart-types.ts +27 -0
  53. package/src/blocks/ecommerce/cart/index.ts +2 -0
  54. package/src/blocks/ecommerce/categories/categories-types.ts +23 -0
  55. package/src/blocks/ecommerce/categories/category-showcase.tsx +125 -0
  56. package/src/blocks/ecommerce/categories/index.ts +2 -0
  57. package/src/blocks/ecommerce/checkout/checkout-types.ts +48 -0
  58. package/src/blocks/ecommerce/checkout/checkout-wizard.tsx +468 -0
  59. package/src/blocks/ecommerce/checkout/index.ts +2 -0
  60. package/src/blocks/ecommerce/comparison/comparison-types.ts +28 -0
  61. package/src/blocks/ecommerce/comparison/index.ts +2 -0
  62. package/src/blocks/ecommerce/comparison/product-comparison.tsx +174 -0
  63. package/src/blocks/ecommerce/coupon-wallet/coupon-wallet-types.ts +21 -0
  64. package/src/blocks/ecommerce/coupon-wallet/coupon-wallet.tsx +154 -0
  65. package/src/blocks/ecommerce/coupon-wallet/index.ts +2 -0
  66. package/src/blocks/ecommerce/delivery-tracker/delivery-tracker-types.ts +20 -0
  67. package/src/blocks/ecommerce/delivery-tracker/delivery-tracker.tsx +169 -0
  68. package/src/blocks/ecommerce/delivery-tracker/index.ts +2 -0
  69. package/src/blocks/ecommerce/index.ts +12 -0
  70. package/src/blocks/ecommerce/payment-methods/index.ts +2 -0
  71. package/src/blocks/ecommerce/payment-methods/payment-method-selector.tsx +224 -0
  72. package/src/blocks/ecommerce/payment-methods/payment-methods-types.ts +21 -0
  73. package/src/blocks/ecommerce/price-history/index.ts +2 -0
  74. package/src/blocks/ecommerce/price-history/price-history-chart.tsx +217 -0
  75. package/src/blocks/ecommerce/price-history/price-history-types.ts +16 -0
  76. package/src/blocks/ecommerce/reviews/index.ts +2 -0
  77. package/src/blocks/ecommerce/reviews/reviews-section.tsx +309 -0
  78. package/src/blocks/ecommerce/reviews/reviews-types.ts +26 -0
  79. package/src/blocks/ecommerce/wishlist/index.ts +2 -0
  80. package/src/blocks/ecommerce/wishlist/wishlist-grid.tsx +149 -0
  81. package/src/blocks/ecommerce/wishlist/wishlist-types.ts +19 -0
  82. package/src/blocks/execution-status/README.md +25 -0
  83. package/src/blocks/execution-status/execution-status.tsx +135 -0
  84. package/src/blocks/execution-status/index.ts +2 -0
  85. package/src/blocks/file-tree/file-icons.tsx +84 -0
  86. package/src/blocks/file-tree/file-tree-node.tsx +158 -0
  87. package/src/blocks/file-tree/file-tree-types.ts +33 -0
  88. package/src/blocks/file-tree/file-tree.tsx +163 -0
  89. package/src/blocks/file-tree/index.ts +5 -0
  90. package/src/blocks/filter-builder/filter-builder.tsx +169 -0
  91. package/src/blocks/filter-builder/filter-row.tsx +164 -0
  92. package/src/blocks/filter-builder/filter-types.ts +61 -0
  93. package/src/blocks/filter-builder/index.ts +12 -0
  94. package/src/blocks/footer/footer-types.ts +35 -0
  95. package/src/blocks/footer/index.ts +2 -0
  96. package/src/blocks/footer/site-footer.tsx +212 -0
  97. package/src/blocks/form/form-block.tsx +113 -0
  98. package/src/blocks/form/form-lookup-field.tsx +60 -0
  99. package/src/blocks/form/index.ts +4 -0
  100. package/src/blocks/handover-stack/handover-stack.tsx +41 -0
  101. package/src/blocks/handover-stack/index.ts +1 -0
  102. package/src/blocks/idea-handover/idea-handover-readiness.ts +41 -0
  103. package/src/blocks/idea-handover/idea-handover-workspace.tsx +150 -0
  104. package/src/blocks/idea-handover/index.ts +4 -0
  105. package/src/blocks/kanban/index.ts +11 -0
  106. package/src/blocks/kanban/kanban-board.tsx +146 -0
  107. package/src/blocks/kanban/kanban-card.tsx +149 -0
  108. package/src/blocks/kanban/kanban-column.tsx +102 -0
  109. package/src/blocks/kanban/kanban-types.ts +38 -0
  110. package/src/blocks/loader/global-loader.tsx +90 -0
  111. package/src/blocks/loader/index.ts +1 -0
  112. package/src/blocks/mascot/README.md +21 -0
  113. package/src/blocks/mascot/index.ts +2 -0
  114. package/src/blocks/mascot/mascot.tsx +202 -0
  115. package/src/blocks/master-list/README.md +21 -0
  116. package/src/blocks/master-list/index.ts +12 -0
  117. package/src/blocks/master-list/master-form.tsx +157 -0
  118. package/src/blocks/master-list/master-list-desk.tsx +370 -0
  119. package/src/blocks/master-list/master-list.tsx +189 -0
  120. package/src/blocks/master-list/types.ts +18 -0
  121. package/src/blocks/mermaid-preview/index.ts +1 -0
  122. package/src/blocks/mermaid-preview/mermaid-preview.tsx +164 -0
  123. package/src/blocks/notifications/README.md +7 -0
  124. package/src/blocks/notifications/index.ts +1 -0
  125. package/src/blocks/notifications/notification-center-page.tsx +73 -0
  126. package/src/blocks/pricing/index.ts +7 -0
  127. package/src/blocks/pricing/pricing-table.tsx +173 -0
  128. package/src/blocks/pricing/pricing-types.ts +34 -0
  129. package/src/blocks/product-card/index.ts +2 -0
  130. package/src/blocks/product-card/product-card-types.ts +35 -0
  131. package/src/blocks/product-card/product-card.tsx +183 -0
  132. package/src/blocks/table/data-table-block.tsx +313 -0
  133. package/src/blocks/table/data-table-filter-menu.tsx +48 -0
  134. package/src/blocks/table/data-table-icon-menu-trigger.tsx +44 -0
  135. package/src/blocks/table/data-table-menu-content.tsx +32 -0
  136. package/src/blocks/table/data-table-pagination.tsx +149 -0
  137. package/src/blocks/table/data-table-row-actions.tsx +53 -0
  138. package/src/blocks/table/data-table-status.tsx +42 -0
  139. package/src/blocks/table/data-table-toolbar.tsx +124 -0
  140. package/src/blocks/table/data-table-totals.tsx +31 -0
  141. package/src/blocks/table/index.ts +13 -0
  142. package/src/blocks/workspace/index.ts +19 -0
  143. package/src/blocks/workspace/workspace-action-card.tsx +73 -0
  144. package/src/blocks/workspace/workspace-application-header.tsx +67 -0
  145. package/src/blocks/workspace/workspace-entity-card.tsx +48 -0
  146. package/src/blocks/workspace/workspace-health-summary.tsx +15 -0
  147. package/src/blocks/workspace/workspace-metric-card.tsx +97 -0
  148. package/src/blocks/workspace/workspace-page-header.tsx +43 -0
  149. package/src/blocks/workspace/workspace-publish-status.tsx +15 -0
  150. package/src/blocks/workspace/workspace-route-checklist.tsx +28 -0
  151. package/src/blocks/workspace/workspace-runtime-status.tsx +48 -0
  152. package/src/blocks/workspace/workspace-section-card.tsx +42 -0
  153. package/src/components/accordion.tsx +65 -0
  154. package/src/components/alert-dialog.tsx +160 -0
  155. package/src/components/alert.tsx +68 -0
  156. package/src/components/aspect-ratio.tsx +22 -0
  157. package/src/components/attachment.tsx +195 -0
  158. package/src/components/avatar.tsx +90 -0
  159. package/src/components/badge.tsx +55 -0
  160. package/src/components/breadcrumb.tsx +100 -0
  161. package/src/components/bubble.tsx +123 -0
  162. package/src/components/button-group.tsx +79 -0
  163. package/src/components/button.tsx +71 -0
  164. package/src/components/calendar.tsx +185 -0
  165. package/src/components/card.tsx +100 -0
  166. package/src/components/carousel.tsx +229 -0
  167. package/src/components/chart.tsx +338 -0
  168. package/src/components/checkbox.tsx +27 -0
  169. package/src/components/collapsible.tsx +15 -0
  170. package/src/components/combobox.tsx +269 -0
  171. package/src/components/command.tsx +181 -0
  172. package/src/components/compact-model-switcher.tsx +128 -0
  173. package/src/components/context-menu.tsx +244 -0
  174. package/src/components/dialog.tsx +138 -0
  175. package/src/components/direction.tsx +1 -0
  176. package/src/components/drawer.tsx +208 -0
  177. package/src/components/dropdown-menu.tsx +257 -0
  178. package/src/components/empty.tsx +93 -0
  179. package/src/components/field.tsx +232 -0
  180. package/src/components/hover-card.tsx +45 -0
  181. package/src/components/input-group.tsx +146 -0
  182. package/src/components/input-otp.tsx +83 -0
  183. package/src/components/input.tsx +19 -0
  184. package/src/components/item.tsx +188 -0
  185. package/src/components/kbd.tsx +26 -0
  186. package/src/components/label.tsx +17 -0
  187. package/src/components/markdown-content.tsx +113 -0
  188. package/src/components/marker.tsx +67 -0
  189. package/src/components/menubar.tsx +267 -0
  190. package/src/components/message-scroller.tsx +91 -0
  191. package/src/components/message.tsx +84 -0
  192. package/src/components/native-select.tsx +54 -0
  193. package/src/components/navigation-menu.tsx +160 -0
  194. package/src/components/pagination.tsx +118 -0
  195. package/src/components/popover.tsx +74 -0
  196. package/src/components/progress.tsx +82 -0
  197. package/src/components/questionnaire.tsx +150 -0
  198. package/src/components/radio-group.tsx +35 -0
  199. package/src/components/resizable.tsx +41 -0
  200. package/src/components/rich-text-editor.tsx +604 -0
  201. package/src/components/scroll-area.tsx +47 -0
  202. package/src/components/select.tsx +189 -0
  203. package/src/components/separator.tsx +18 -0
  204. package/src/components/sheet.tsx +125 -0
  205. package/src/components/sidebar.tsx +693 -0
  206. package/src/components/skeleton.tsx +13 -0
  207. package/src/components/slider.tsx +51 -0
  208. package/src/components/sonner.tsx +45 -0
  209. package/src/components/sparkline.tsx +231 -0
  210. package/src/components/spinner.tsx +20 -0
  211. package/src/components/status-badge.tsx +114 -0
  212. package/src/components/switch.tsx +31 -0
  213. package/src/components/table.tsx +91 -0
  214. package/src/components/tabs.tsx +72 -0
  215. package/src/components/textarea.tsx +17 -0
  216. package/src/components/toast.tsx +222 -0
  217. package/src/components/toggle-group.tsx +85 -0
  218. package/src/components/toggle.tsx +42 -0
  219. package/src/components/tooltip.tsx +53 -0
  220. package/src/design-system/README.md +55 -0
  221. package/src/design-system/action-intents.ts +21 -0
  222. package/src/design-system/agent-manifest.ts +83 -0
  223. package/src/design-system/block-registry.ts +272 -0
  224. package/src/design-system/component-registry.ts +142 -0
  225. package/src/design-system/contracts.ts +69 -0
  226. package/src/design-system/defaults.ts +3 -0
  227. package/src/design-system/index.ts +29 -0
  228. package/src/design-system/mcp-server.ts +156 -0
  229. package/src/design-system/page-registry.ts +74 -0
  230. package/src/design-system/selection.ts +67 -0
  231. package/src/design-system/template-registry.ts +33 -0
  232. package/src/features/interface-topology/index.ts +14 -0
  233. package/src/features/interface-topology/interface-topology.types.ts +40 -0
  234. package/src/features/interface-topology/topology-control.tsx +18 -0
  235. package/src/features/interface-topology/topology-inspector.tsx +228 -0
  236. package/src/features/interface-topology/topology-marker.tsx +47 -0
  237. package/src/features/interface-topology/topology-region.tsx +44 -0
  238. package/src/features/interface-topology/use-interface-topology.ts +178 -0
  239. package/src/hooks/use-mobile.ts +19 -0
  240. package/src/hooks/use-scroll-follow.ts +22 -0
  241. package/src/index.ts +11 -0
  242. package/src/layouts/README.md +65 -0
  243. package/src/layouts/agent-workspace/agent-activity-rail.tsx +101 -0
  244. package/src/layouts/agent-workspace/agent-workspace.tsx +27 -0
  245. package/src/layouts/agent-workspace/agent-workspace.types.ts +28 -0
  246. package/src/layouts/agent-workspace/index.ts +7 -0
  247. package/src/layouts/blog-header/blog-header-types.ts +25 -0
  248. package/src/layouts/blog-header/blog-header.tsx +172 -0
  249. package/src/layouts/blog-header/index.ts +2 -0
  250. package/src/layouts/documentation-workspace/README.md +8 -0
  251. package/src/layouts/documentation-workspace/documentation-workspace.tsx +34 -0
  252. package/src/layouts/documentation-workspace/index.ts +1 -0
  253. package/src/layouts/ecommerce-header/ecommerce-actions.tsx +89 -0
  254. package/src/layouts/ecommerce-header/ecommerce-announcement.tsx +43 -0
  255. package/src/layouts/ecommerce-header/ecommerce-drawer.tsx +97 -0
  256. package/src/layouts/ecommerce-header/ecommerce-header-types.ts +59 -0
  257. package/src/layouts/ecommerce-header/ecommerce-header.tsx +47 -0
  258. package/src/layouts/ecommerce-header/ecommerce-navigation.tsx +98 -0
  259. package/src/layouts/ecommerce-header/ecommerce-search.tsx +86 -0
  260. package/src/layouts/ecommerce-header/index.ts +2 -0
  261. package/src/layouts/main-workspace/index.ts +28 -0
  262. package/src/layouts/mdi/index.ts +1 -0
  263. package/src/layouts/mdi/mdi.tsx +20 -0
  264. package/src/layouts/mdi-main/index.ts +27 -0
  265. package/src/layouts/mdi-main/mdi-app-catalog.generated.ts +159 -0
  266. package/src/layouts/mdi-main/mdi-app-catalog.ts +76 -0
  267. package/src/layouts/mdi-main/mdi-app-switcher.tsx +107 -0
  268. package/src/layouts/mdi-main/mdi-feature-settings.tsx +135 -0
  269. package/src/layouts/mdi-main/mdi-global-search.tsx +192 -0
  270. package/src/layouts/mdi-main/mdi-main.tsx +241 -0
  271. package/src/layouts/mdi-main/mdi-notifications-menu.tsx +197 -0
  272. package/src/layouts/mdi-main/mdi-profile-menu.tsx +79 -0
  273. package/src/layouts/mdi-main/mdi-sidebar-navigation.tsx +226 -0
  274. package/src/layouts/mdi-main/mdi-sidebar.tsx +110 -0
  275. package/src/layouts/mdi-main/mdi-status-bar.tsx +26 -0
  276. package/src/layouts/mdi-main/mdi-top-menu-control.ts +4 -0
  277. package/src/layouts/mdi-main/mdi-top-menu.tsx +117 -0
  278. package/src/layouts/mdi-main/mdi-topology.tsx +297 -0
  279. package/src/layouts/mdi-main/mdi-tweak-panel.tsx +104 -0
  280. package/src/layouts/mdi-main/mdi-types.ts +131 -0
  281. package/src/layouts/mdi-main/use-mdi-features.ts +36 -0
  282. package/src/layouts/mdi-main/use-mdi-sidebar-state.ts +59 -0
  283. package/src/layouts/site-header/announcement-bar.tsx +55 -0
  284. package/src/layouts/site-header/category-strip.tsx +41 -0
  285. package/src/layouts/site-header/index.ts +16 -0
  286. package/src/layouts/site-header/main-navbar.tsx +179 -0
  287. package/src/layouts/site-header/mobile-drawer.tsx +140 -0
  288. package/src/layouts/site-header/site-header-types.ts +63 -0
  289. package/src/layouts/site-header/site-header.tsx +58 -0
  290. package/src/lib/random-id.ts +19 -0
  291. package/src/lib/utils.ts +6 -0
  292. package/src/providers/pdf-provider.ts +137 -0
  293. package/src/styles/globals.css +374 -0
  294. package/src/templates/README.md +12 -0
  295. package/src/templates/dashboard-01/components/app-sidebar.tsx +177 -0
  296. package/src/templates/dashboard-01/components/chart-area-interactive.tsx +276 -0
  297. package/src/templates/dashboard-01/components/data-table-cell-viewer.tsx +213 -0
  298. package/src/templates/dashboard-01/components/data-table.tsx +577 -0
  299. package/src/templates/dashboard-01/components/nav-documents.tsx +79 -0
  300. package/src/templates/dashboard-01/components/nav-main.tsx +55 -0
  301. package/src/templates/dashboard-01/components/nav-secondary.tsx +39 -0
  302. package/src/templates/dashboard-01/components/nav-user.tsx +97 -0
  303. package/src/templates/dashboard-01/components/section-cards.tsx +99 -0
  304. package/src/templates/dashboard-01/components/site-header.tsx +14 -0
  305. package/src/templates/dashboard-01/data.json +614 -0
  306. package/src/templates/dashboard-01/index.ts +7 -0
  307. package/src/templates/documentation-sidebar/app-sidebar.tsx +95 -0
  308. package/src/templates/documentation-sidebar/index.ts +2 -0
  309. package/src/templates/sidebar-07/components/app-sidebar.tsx +175 -0
  310. package/src/templates/sidebar-07/components/nav-main.tsx +64 -0
  311. package/src/templates/sidebar-07/components/nav-projects.tsx +79 -0
  312. package/src/templates/sidebar-07/components/nav-user.tsx +105 -0
  313. package/src/templates/sidebar-07/components/team-switcher.tsx +94 -0
  314. package/src/templates/sidebar-07/index.ts +1 -0
  315. package/src/templates/ui-page/index.ts +7 -0
  316. package/src/templates/ui-page/ui-browser-frame.tsx +27 -0
  317. package/src/templates/ui-page/ui-template-code.tsx +48 -0
  318. package/src/templates/ui-page/ui-template-header.tsx +87 -0
  319. package/src/templates/ui-page/ui-template-navigation.tsx +60 -0
  320. package/src/templates/ui-page/ui-template-page.tsx +97 -0
  321. package/src/theme/index.ts +12 -0
  322. package/src/theme/theme-config.ts +30 -0
  323. package/src/theme/theme-provider.tsx +142 -0
  324. package/src/theme/theme-selector.tsx +75 -0
  325. package/src/tokens/index.ts +15 -0
  326. package/src/vite-env.d.ts +3 -0
@@ -0,0 +1,41 @@
1
+ import { cn } from '../../lib/utils'
2
+ import type { SiteHeaderCategory } from './site-header-types'
3
+
4
+ export interface CategoryStripProps {
5
+ categories: readonly SiteHeaderCategory[]
6
+ className?: string
7
+ onSelect?: (category: SiteHeaderCategory) => void
8
+ }
9
+
10
+ export function CategoryStrip({ categories, className, onSelect }: CategoryStripProps) {
11
+ if (!categories || categories.length === 0) return null
12
+
13
+ return (
14
+ <div className={cn('w-full border-t border-border/40 bg-muted/20 select-none', className)}>
15
+ <div className="mx-auto flex h-10 w-full max-w-7xl items-center overflow-x-auto px-4 scrollbar-none sm:px-6 lg:px-8">
16
+ <nav aria-label="Category navigation" className="flex items-center gap-1.5 min-w-max">
17
+ {categories.map((cat) => (
18
+ <a
19
+ className={cn(
20
+ 'rounded-lg px-2.5 py-1 text-xs font-medium transition-colors',
21
+ cat.active
22
+ ? 'bg-background text-foreground font-semibold shadow-2xs'
23
+ : 'text-muted-foreground hover:bg-muted/60 hover:text-foreground',
24
+ )}
25
+ href={cat.href}
26
+ key={cat.id}
27
+ onClick={(e) => {
28
+ if (onSelect) {
29
+ e.preventDefault()
30
+ onSelect(cat)
31
+ }
32
+ }}
33
+ >
34
+ {cat.label}
35
+ </a>
36
+ ))}
37
+ </nav>
38
+ </div>
39
+ </div>
40
+ )
41
+ }
@@ -0,0 +1,16 @@
1
+ export { SiteHeader } from './site-header'
2
+ export { AnnouncementBar } from './announcement-bar'
3
+ export { MainNavbar } from './main-navbar'
4
+ export { CategoryStrip } from './category-strip'
5
+ export { MobileDrawer } from './mobile-drawer'
6
+ export type {
7
+ SiteAnnouncementProps,
8
+ SiteHeaderActions,
9
+ SiteHeaderBrand,
10
+ SiteHeaderCategory,
11
+ SiteHeaderNavLink,
12
+ SiteHeaderProps,
13
+ } from './site-header-types'
14
+ export type { MainNavbarProps } from './main-navbar'
15
+ export type { CategoryStripProps } from './category-strip'
16
+ export type { MobileDrawerProps } from './mobile-drawer'
@@ -0,0 +1,179 @@
1
+ import { Menu, Search, ShoppingBag, SunMoon, User } from 'lucide-react'
2
+ import { Badge } from '../../components/badge'
3
+ import { Button } from '../../components/button'
4
+ import { cn } from '../../lib/utils'
5
+ import type { SiteHeaderActions, SiteHeaderBrand, SiteHeaderNavLink } from './site-header-types'
6
+
7
+ export interface MainNavbarProps {
8
+ actions?: SiteHeaderActions
9
+ brand: SiteHeaderBrand
10
+ links?: readonly SiteHeaderNavLink[]
11
+ onOpenMobileMenu?: () => void
12
+ }
13
+
14
+ export function MainNavbar({ actions = {}, brand, links = [], onOpenMobileMenu }: MainNavbarProps) {
15
+ const {
16
+ accountHref = '/account',
17
+ cartCount = 0,
18
+ ctaHref,
19
+ ctaLabel = 'Get Started',
20
+ onAccountClick,
21
+ onCartClick,
22
+ onCtaClick,
23
+ onSearchClick,
24
+ onThemeToggle,
25
+ searchPlaceholder = 'Search...',
26
+ showAccount = true,
27
+ showCart = true,
28
+ showCta = true,
29
+ showSearch = true,
30
+ showThemeToggle = true,
31
+ } = actions
32
+
33
+ return (
34
+ <div className="mx-auto flex h-16 w-full max-w-7xl items-center justify-between gap-4 px-4 sm:px-6 lg:px-8">
35
+ {/* Brand */}
36
+ <div className="flex items-center gap-3">
37
+ <a
38
+ className="flex items-center gap-2.5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
39
+ href={brand.href ?? '/'}
40
+ >
41
+ {brand.logo ? (
42
+ <div className="flex size-9 shrink-0 items-center justify-center rounded-xl bg-primary text-primary-foreground font-bold">
43
+ {brand.logo}
44
+ </div>
45
+ ) : (
46
+ <div className="flex size-9 shrink-0 items-center justify-center rounded-xl bg-primary text-primary-foreground font-bold text-sm">
47
+ {brand.title.slice(0, 2).toUpperCase()}
48
+ </div>
49
+ )}
50
+
51
+ <div className="flex flex-col">
52
+ <div className="flex items-center gap-2">
53
+ <span className="text-base font-bold tracking-tight text-foreground">
54
+ {brand.title}
55
+ </span>
56
+ {brand.badge && (
57
+ <Badge className="text-[10px] font-semibold" variant="secondary">
58
+ {brand.badge}
59
+ </Badge>
60
+ )}
61
+ </div>
62
+ {brand.tagline && (
63
+ <span className="text-[11px] text-muted-foreground hidden sm:inline-block">
64
+ {brand.tagline}
65
+ </span>
66
+ )}
67
+ </div>
68
+ </a>
69
+ </div>
70
+
71
+ {/* Centered Navigation */}
72
+ {links.length > 0 && (
73
+ <nav aria-label="Main navigation" className="hidden md:flex items-center gap-1">
74
+ {links.map((link) => (
75
+ <a
76
+ className={cn(
77
+ 'relative flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-sm font-medium transition-colors select-none',
78
+ link.active
79
+ ? 'text-primary font-semibold'
80
+ : 'text-muted-foreground hover:bg-muted/60 hover:text-foreground',
81
+ )}
82
+ href={link.href}
83
+ key={link.href}
84
+ rel={link.external ? 'noopener noreferrer' : undefined}
85
+ target={link.external ? '_blank' : undefined}
86
+ >
87
+ {link.label}
88
+ {link.badge && (
89
+ <span className="rounded-full bg-primary/10 px-1.5 py-0.2 text-[10px] font-semibold text-primary">
90
+ {link.badge}
91
+ </span>
92
+ )}
93
+ </a>
94
+ ))}
95
+ </nav>
96
+ )}
97
+
98
+ {/* Action Cluster */}
99
+ <div className="flex items-center gap-1.5 sm:gap-2">
100
+ {showSearch && (
101
+ <button
102
+ aria-label="Search"
103
+ className="flex items-center gap-2 rounded-xl border border-border/80 bg-muted/40 px-2.5 py-1.5 text-xs text-muted-foreground transition-colors hover:border-foreground/30 hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
104
+ type="button"
105
+ onClick={onSearchClick}
106
+ >
107
+ <Search className="size-4" />
108
+ <span className="hidden lg:inline-block">{searchPlaceholder}</span>
109
+ <kbd className="hidden lg:inline-flex rounded border bg-background px-1 text-[10px] font-mono">
110
+ ⌘K
111
+ </kbd>
112
+ </button>
113
+ )}
114
+
115
+ {showThemeToggle && onThemeToggle && (
116
+ <Button
117
+ aria-label="Toggle theme mode"
118
+ className="size-8.5 rounded-xl text-muted-foreground hover:text-foreground"
119
+ size="icon"
120
+ variant="ghost"
121
+ onClick={onThemeToggle}
122
+ >
123
+ <SunMoon className="size-4" />
124
+ </Button>
125
+ )}
126
+
127
+ {showAccount && (
128
+ <a
129
+ aria-label="Account profile"
130
+ className="inline-flex size-8.5 items-center justify-center rounded-xl text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
131
+ href={accountHref}
132
+ onClick={onAccountClick}
133
+ >
134
+ <User className="size-4" />
135
+ </a>
136
+ )}
137
+
138
+ {showCart && (
139
+ <button
140
+ aria-label={`Shopping cart with ${cartCount} items`}
141
+ className="relative inline-flex size-8.5 items-center justify-center rounded-xl text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
142
+ type="button"
143
+ onClick={onCartClick}
144
+ >
145
+ <ShoppingBag className="size-4" />
146
+ {cartCount > 0 && (
147
+ <span className="absolute -right-1 -top-1 flex size-4.5 items-center justify-center rounded-full bg-primary text-[10px] font-bold text-primary-foreground shadow-xs animate-in zoom-in">
148
+ {cartCount > 99 ? '99+' : cartCount}
149
+ </span>
150
+ )}
151
+ </button>
152
+ )}
153
+
154
+ {showCta && (
155
+ <Button
156
+ className="hidden sm:inline-flex rounded-xl font-semibold"
157
+ render={ctaHref ? <a href={ctaHref} /> : undefined}
158
+ size="sm"
159
+ variant="default"
160
+ onClick={onCtaClick}
161
+ >
162
+ {ctaLabel}
163
+ </Button>
164
+ )}
165
+
166
+ {/* Mobile menu trigger */}
167
+ <Button
168
+ aria-label="Open navigation menu"
169
+ className="md:hidden size-8.5 rounded-xl text-muted-foreground hover:text-foreground"
170
+ size="icon"
171
+ variant="ghost"
172
+ onClick={onOpenMobileMenu}
173
+ >
174
+ <Menu className="size-4.5" />
175
+ </Button>
176
+ </div>
177
+ </div>
178
+ )
179
+ }
@@ -0,0 +1,140 @@
1
+ import { Search, X } from 'lucide-react'
2
+ import { Button } from '../../components/button'
3
+ import { cn } from '../../lib/utils'
4
+ import type {
5
+ SiteHeaderActions,
6
+ SiteHeaderBrand,
7
+ SiteHeaderCategory,
8
+ SiteHeaderNavLink,
9
+ } from './site-header-types'
10
+
11
+ export interface MobileDrawerProps {
12
+ actions?: SiteHeaderActions
13
+ brand: SiteHeaderBrand
14
+ categories?: readonly SiteHeaderCategory[]
15
+ isOpen: boolean
16
+ links?: readonly SiteHeaderNavLink[]
17
+ onClose: () => void
18
+ }
19
+
20
+ export function MobileDrawer({
21
+ actions = {},
22
+ brand,
23
+ categories = [],
24
+ isOpen,
25
+ links = [],
26
+ onClose,
27
+ }: MobileDrawerProps) {
28
+ if (!isOpen) return null
29
+
30
+ return (
31
+ <div
32
+ aria-label="Mobile navigation"
33
+ aria-modal="true"
34
+ className="fixed inset-0 z-50 flex md:hidden"
35
+ role="dialog"
36
+ >
37
+ {/* Backdrop */}
38
+ <div
39
+ className="fixed inset-0 bg-background/80 backdrop-blur-xs transition-opacity"
40
+ onClick={onClose}
41
+ />
42
+
43
+ {/* Drawer surface */}
44
+ <div className="relative ml-auto flex h-full w-full max-w-xs flex-col gap-4 border-l border-border/80 bg-background p-5 shadow-xl animate-in slide-in-from-right duration-200">
45
+ <div className="flex items-center justify-between border-b border-border/60 pb-3">
46
+ <span className="font-bold tracking-tight text-foreground">{brand.title}</span>
47
+ <button
48
+ aria-label="Close navigation"
49
+ className="rounded-lg p-1.5 text-muted-foreground hover:bg-muted hover:text-foreground"
50
+ type="button"
51
+ onClick={onClose}
52
+ >
53
+ <X className="size-5" />
54
+ </button>
55
+ </div>
56
+
57
+ {actions.showSearch && (
58
+ <div className="relative">
59
+ <Search className="absolute left-2.5 top-1/2 size-3.5 -translate-y-1/2 text-muted-foreground/60" />
60
+ <input
61
+ className="h-9 w-full rounded-xl border border-border/80 bg-muted/30 pl-8 pr-3 text-xs text-foreground placeholder:text-muted-foreground/60 focus:border-ring focus:bg-background focus:outline-none"
62
+ placeholder={actions.searchPlaceholder || 'Search products & projects...'}
63
+ type="text"
64
+ />
65
+ </div>
66
+ )}
67
+
68
+ <div className="flex flex-1 flex-col gap-4 overflow-y-auto pr-1">
69
+ {/* Main links */}
70
+ <nav className="flex flex-col gap-1">
71
+ <span className="px-2 text-[11px] font-semibold text-muted-foreground uppercase tracking-wider">
72
+ Navigation
73
+ </span>
74
+ {links.map((link) => (
75
+ <a
76
+ className={cn(
77
+ 'flex items-center justify-between rounded-lg px-3 py-2 text-sm font-medium transition-colors',
78
+ link.active
79
+ ? 'bg-primary/10 text-primary font-semibold'
80
+ : 'text-foreground/80 hover:bg-muted hover:text-foreground',
81
+ )}
82
+ href={link.href}
83
+ key={link.href}
84
+ onClick={onClose}
85
+ >
86
+ <span>{link.label}</span>
87
+ {link.badge && (
88
+ <span className="rounded-full bg-primary/10 px-1.5 py-0.5 text-[10px] font-semibold text-primary">
89
+ {link.badge}
90
+ </span>
91
+ )}
92
+ </a>
93
+ ))}
94
+ </nav>
95
+
96
+ {/* Categories */}
97
+ {categories.length > 0 && (
98
+ <div className="flex flex-col gap-1 border-t border-border/40 pt-3">
99
+ <span className="px-2 text-[11px] font-semibold text-muted-foreground uppercase tracking-wider">
100
+ Categories
101
+ </span>
102
+ <div className="flex flex-wrap gap-1.5 px-2 pt-1">
103
+ {categories.map((cat) => (
104
+ <a
105
+ className={cn(
106
+ 'rounded-md px-2.5 py-1 text-xs font-medium transition-colors',
107
+ cat.active
108
+ ? 'bg-primary text-primary-foreground font-semibold'
109
+ : 'bg-muted/70 text-foreground/80 hover:bg-muted',
110
+ )}
111
+ href={cat.href}
112
+ key={cat.id}
113
+ onClick={onClose}
114
+ >
115
+ {cat.label}
116
+ </a>
117
+ ))}
118
+ </div>
119
+ </div>
120
+ )}
121
+ </div>
122
+
123
+ {actions.showCta && (
124
+ <div className="border-t border-border/60 pt-3">
125
+ <Button
126
+ className="w-full rounded-xl font-semibold"
127
+ render={actions.ctaHref ? <a href={actions.ctaHref} /> : undefined}
128
+ onClick={() => {
129
+ actions.onCtaClick?.()
130
+ onClose()
131
+ }}
132
+ >
133
+ {actions.ctaLabel || 'Get Started'}
134
+ </Button>
135
+ </div>
136
+ )}
137
+ </div>
138
+ </div>
139
+ )
140
+ }
@@ -0,0 +1,63 @@
1
+ import type { ReactNode } from 'react'
2
+
3
+ export interface SiteHeaderNavLink {
4
+ active?: boolean
5
+ badge?: string
6
+ external?: boolean
7
+ href: string
8
+ label: string
9
+ }
10
+
11
+ export interface SiteHeaderCategory {
12
+ active?: boolean
13
+ href: string
14
+ id: string
15
+ label: string
16
+ }
17
+
18
+ export interface SiteAnnouncementProps {
19
+ actionLabel?: string
20
+ actionUrl?: string
21
+ dismissible?: boolean
22
+ message: string
23
+ onDismiss?: () => void
24
+ show?: boolean
25
+ }
26
+
27
+ export interface SiteHeaderBrand {
28
+ badge?: string
29
+ href?: string
30
+ logo?: ReactNode
31
+ tagline?: string
32
+ title: string
33
+ }
34
+
35
+ export interface SiteHeaderActions {
36
+ accountHref?: string
37
+ cartCount?: number
38
+ ctaHref?: string
39
+ ctaLabel?: string
40
+ onAccountClick?: () => void
41
+ onCartClick?: () => void
42
+ onCtaClick?: () => void
43
+ onSearchClick?: () => void
44
+ onThemeToggle?: () => void
45
+ searchPlaceholder?: string
46
+ showAccount?: boolean
47
+ showCart?: boolean
48
+ showCta?: boolean
49
+ showSearch?: boolean
50
+ showThemeToggle?: boolean
51
+ }
52
+
53
+ export interface SiteHeaderProps {
54
+ actions?: SiteHeaderActions
55
+ announcement?: SiteAnnouncementProps
56
+ brand: SiteHeaderBrand
57
+ categories?: readonly SiteHeaderCategory[]
58
+ className?: string
59
+ links?: readonly SiteHeaderNavLink[]
60
+ onCategorySelect?: (category: SiteHeaderCategory) => void
61
+ showCategories?: boolean
62
+ sticky?: boolean
63
+ }
@@ -0,0 +1,58 @@
1
+ import { useState } from 'react'
2
+ import { cn } from '../../lib/utils'
3
+ import { AnnouncementBar } from './announcement-bar'
4
+ import { CategoryStrip } from './category-strip'
5
+ import { MainNavbar } from './main-navbar'
6
+ import { MobileDrawer } from './mobile-drawer'
7
+ import type { SiteHeaderProps } from './site-header-types'
8
+
9
+ export function SiteHeader({
10
+ actions,
11
+ announcement,
12
+ brand,
13
+ categories = [],
14
+ className,
15
+ links = [],
16
+ onCategorySelect,
17
+ showCategories = true,
18
+ sticky = true,
19
+ }: SiteHeaderProps) {
20
+ const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
21
+
22
+ return (
23
+ <header
24
+ className={cn(
25
+ 'w-full flex flex-col z-40 transition-shadow',
26
+ sticky &&
27
+ 'sticky top-0 bg-background/90 backdrop-blur-md border-b border-border/70 shadow-2xs',
28
+ className,
29
+ )}
30
+ >
31
+ {/* 1. Top Announcement Bar */}
32
+ {announcement && <AnnouncementBar {...announcement} />}
33
+
34
+ {/* 2. Primary Navigation Bar */}
35
+ <MainNavbar
36
+ actions={actions}
37
+ brand={brand}
38
+ links={links}
39
+ onOpenMobileMenu={() => setMobileMenuOpen(true)}
40
+ />
41
+
42
+ {/* 3. Sub-navigation Category Strip */}
43
+ {showCategories && categories.length > 0 && (
44
+ <CategoryStrip categories={categories} onSelect={onCategorySelect} />
45
+ )}
46
+
47
+ {/* 4. Responsive Mobile Drawer */}
48
+ <MobileDrawer
49
+ actions={actions}
50
+ brand={brand}
51
+ categories={categories}
52
+ isOpen={mobileMenuOpen}
53
+ links={links}
54
+ onClose={() => setMobileMenuOpen(false)}
55
+ />
56
+ </header>
57
+ )
58
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Returns an RFC 4122 version 4 identifier without requiring `crypto.randomUUID`.
3
+ *
4
+ * Older browsers can provide `getRandomValues` but not `randomUUID`; the final
5
+ * fallback keeps non-security-critical client correlation IDs working as well.
6
+ */
7
+ export function createRandomId(): string {
8
+ const browserCrypto = globalThis.crypto;
9
+ if (typeof browserCrypto?.randomUUID === "function") return browserCrypto.randomUUID();
10
+
11
+ const bytes = new Uint8Array(16);
12
+ if (typeof browserCrypto?.getRandomValues === "function") browserCrypto.getRandomValues(bytes);
13
+ else for (let index = 0; index < bytes.length; index += 1) bytes[index] = Math.floor(Math.random() * 256);
14
+
15
+ bytes[6] = (bytes[6] & 0x0f) | 0x40;
16
+ bytes[8] = (bytes[8] & 0x3f) | 0x80;
17
+ const hex = Array.from(bytes, (value) => value.toString(16).padStart(2, "0")).join("");
18
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
19
+ }
@@ -0,0 +1,6 @@
1
+ import { type ClassValue, clsx } from 'clsx'
2
+ import { twMerge } from 'tailwind-merge'
3
+
4
+ export function cn(...inputs: ClassValue[]): string {
5
+ return twMerge(clsx(inputs))
6
+ }
@@ -0,0 +1,137 @@
1
+ import { jsPDF } from "jspdf";
2
+
3
+ export type PdfDocumentSource = {
4
+ content: string;
5
+ title: string;
6
+ };
7
+
8
+ export type PdfShareChannel = "email" | "whatsapp";
9
+
10
+ export class PdfProvider {
11
+ static async copyText(content: string): Promise<void> {
12
+ if (navigator.clipboard?.writeText) {
13
+ try {
14
+ await navigator.clipboard.writeText(content);
15
+ return;
16
+ } catch {
17
+ // Browser clipboard permissions can be unavailable in embedded desktop webviews.
18
+ }
19
+ }
20
+
21
+ const textarea = window.document.createElement("textarea");
22
+ textarea.value = content;
23
+ textarea.style.position = "fixed";
24
+ textarea.style.opacity = "0";
25
+ window.document.body.append(textarea);
26
+ textarea.select();
27
+ window.document.execCommand("copy");
28
+ textarea.remove();
29
+ }
30
+
31
+ static async shareText(source: PdfDocumentSource): Promise<void> {
32
+ if (navigator.share) {
33
+ try {
34
+ await navigator.share({ text: source.content, title: source.title });
35
+ return;
36
+ } catch (error) {
37
+ if ((error as { name?: string }).name === "AbortError") return;
38
+ }
39
+ }
40
+ await this.copyText(source.content);
41
+ }
42
+
43
+ static async create(source: PdfDocumentSource): Promise<File> {
44
+ const document = new jsPDF({ format: "a4", unit: "mm" });
45
+ const margin = 18;
46
+ const pageWidth = document.internal.pageSize.getWidth() - margin * 2;
47
+ const pageHeight = document.internal.pageSize.getHeight() - margin * 2;
48
+ const title = source.title.trim() || "Zetro response";
49
+ const lines = document.splitTextToSize(toPlainText(source.content), pageWidth);
50
+ let cursor = margin;
51
+
52
+ document.setFont("helvetica", "bold");
53
+ document.setFontSize(16);
54
+ document.text(title, margin, cursor);
55
+ cursor += 10;
56
+ document.setFont("helvetica", "normal");
57
+ document.setFontSize(10);
58
+
59
+ for (const line of lines) {
60
+ if (cursor > pageHeight) {
61
+ document.addPage();
62
+ cursor = margin;
63
+ }
64
+ document.text(line, margin, cursor);
65
+ cursor += 5;
66
+ }
67
+
68
+ const pageCount = document.getNumberOfPages();
69
+ for (let page = 1; page <= pageCount; page += 1) {
70
+ document.setPage(page);
71
+ document.setFontSize(8);
72
+ document.setTextColor(110);
73
+ document.text(`Zetro · ${page} / ${pageCount}`, margin, document.internal.pageSize.getHeight() - 10);
74
+ document.setTextColor(0);
75
+ }
76
+
77
+ return new File([document.output("blob")], `${toFileName(title)}.pdf`, { type: "application/pdf" });
78
+ }
79
+
80
+ static async download(source: PdfDocumentSource): Promise<File> {
81
+ const file = await this.create(source);
82
+ const url = URL.createObjectURL(file);
83
+ const anchor = window.document.createElement("a");
84
+ anchor.href = url;
85
+ anchor.download = file.name;
86
+ anchor.click();
87
+ window.setTimeout(() => URL.revokeObjectURL(url), 0);
88
+ return file;
89
+ }
90
+
91
+ static print(source: PdfDocumentSource): void {
92
+ const printWindow = window.open("", "_blank", "popup");
93
+ if (!printWindow) throw new Error("Allow pop-ups to print this response.");
94
+ printWindow.document.write(printDocument(source));
95
+ printWindow.document.close();
96
+ printWindow.focus();
97
+ printWindow.print();
98
+ }
99
+
100
+ static async share(source: PdfDocumentSource, channel: PdfShareChannel): Promise<void> {
101
+ const file = await this.create(source);
102
+ if (navigator.share && navigator.canShare?.({ files: [file] })) {
103
+ await navigator.share({ files: [file], text: source.title, title: source.title });
104
+ return;
105
+ }
106
+
107
+ await this.download(source);
108
+ const message = encodeURIComponent(`${source.title}\n\nThe PDF has downloaded and is ready to attach.`);
109
+ if (channel === "email") {
110
+ window.location.assign(`mailto:?subject=${encodeURIComponent(source.title)}&body=${message}`);
111
+ return;
112
+ }
113
+ window.open(`https://wa.me/?text=${message}`, "_blank", "noopener,noreferrer");
114
+ }
115
+ }
116
+
117
+ function toPlainText(value: string): string {
118
+ const parser = new DOMParser();
119
+ return (parser.parseFromString(value, "text/html").body.textContent || value)
120
+ .replace(/```[\s\S]*?```/g, (block) => block.replace(/```/g, ""))
121
+ .replace(/[`*_#]/g, "")
122
+ .replace(/\n{3,}/g, "\n\n")
123
+ .trim();
124
+ }
125
+
126
+ function toFileName(value: string): string {
127
+ return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "zetro-response";
128
+ }
129
+
130
+ function printDocument(source: PdfDocumentSource): string {
131
+ const content = escapeHtml(source.content).replace(/\n/g, "<br />");
132
+ return `<!doctype html><html><head><title>${escapeHtml(source.title)}</title><style>@page { margin: 18mm; } body { color: #111; font: 11pt/1.6 Arial, sans-serif; } h1 { font-size: 18pt; margin: 0 0 12mm; } article { white-space: normal; } </style></head><body><h1>${escapeHtml(source.title)}</h1><article>${content}</article></body></html>`;
133
+ }
134
+
135
+ function escapeHtml(value: string): string {
136
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
137
+ }