@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,178 @@
1
+ import { useCallback, useEffect, useMemo, useState } from 'react';
2
+ import type {
3
+ InterfaceTopologyController,
4
+ InterfaceTopologyDesk,
5
+ InterfaceTopologySection,
6
+ } from './interface-topology.types';
7
+
8
+ const labelsStorageKey = 'codexsun.ui.ito.labels-visible';
9
+
10
+ export function useInterfaceTopology(desks: readonly InterfaceTopologyDesk[]): InterfaceTopologyController {
11
+ validateInterfaceTopologyDesks(desks);
12
+ const allSections = useMemo(() => desks.flatMap((desk) => desk.sections), [desks]);
13
+ const [scopeRootId, setScopeRootId] = useState<string | null>(null);
14
+ const scopedDesks = useMemo(
15
+ () =>
16
+ scopeRootId
17
+ ? desks
18
+ .map((desk) => ({
19
+ ...desk,
20
+ sections: desk.sections.filter(({ id }) => id === scopeRootId || id.startsWith(`${scopeRootId}.`)),
21
+ }))
22
+ .filter((desk) => desk.sections.length > 0)
23
+ : desks,
24
+ [desks, scopeRootId],
25
+ );
26
+ const firstDesk = scopedDesks[0];
27
+ const [activeDeskId, setActiveDeskId] = useState(firstDesk?.id ?? '');
28
+ const activeDesk = scopedDesks.find(({ id }) => id === activeDeskId) ?? firstDesk;
29
+ const sections = activeDesk?.sections ?? [];
30
+ const firstId = sections[0]?.id ?? '';
31
+ const [labelsVisible, setLabelsVisible] = useState(readLabelsVisibility);
32
+ const [open, setOpen] = useState(false);
33
+ const [highlighting, setHighlighting] = useState(false);
34
+ const [selected, setSelected] = useState(firstId);
35
+ const sectionIds = useMemo(
36
+ () => new Set(scopedDesks.flatMap((desk) => desk.sections.map(({ id }) => id))),
37
+ [scopedDesks],
38
+ );
39
+ const pageKey = scopedDesks
40
+ .map((desk) => `${desk.id}:${desk.sections.map(({ technicalName }) => technicalName).join('|')}`)
41
+ .join(';');
42
+
43
+ useEffect(() => setHighlighting(false), [pageKey]);
44
+ useEffect(() => {
45
+ if (!scopedDesks.some(({ id }) => id === activeDeskId)) setActiveDeskId(firstDesk?.id ?? '');
46
+ }, [activeDeskId, firstDesk?.id, scopedDesks]);
47
+ useEffect(() => {
48
+ if (!sections.some(({ id }) => id === selected)) setSelected(firstId);
49
+ }, [firstId, sections, selected]);
50
+
51
+ function inspect(id: string) {
52
+ const owner = scopedDesks.find((desk) => desk.sections.some((section) => section.id === id));
53
+ if (!owner) return;
54
+ setActiveDeskId(owner.id);
55
+ copyTechnicalName(id);
56
+ setSelected(id);
57
+ setHighlighting(true);
58
+ setOpen(true);
59
+ }
60
+
61
+ function copyTechnicalName(id: string) {
62
+ const section = allSections.find((candidate) => candidate.id === id);
63
+ if (section) copyText(section.technicalName);
64
+ }
65
+
66
+ function selectDesk(id: string) {
67
+ const desk = scopedDesks.find((candidate) => candidate.id === id);
68
+ if (!desk) return;
69
+ setActiveDeskId(id);
70
+ setSelected(desk.sections[0]?.id ?? '');
71
+ setHighlighting(false);
72
+ }
73
+
74
+ function toggleLabels() {
75
+ setLabelsVisible((current) => {
76
+ const next = !current;
77
+ persistLabelsVisibility(next);
78
+ return next;
79
+ });
80
+ }
81
+
82
+ const setScope = useCallback((rootId: string | null) => setScopeRootId(rootId), []);
83
+
84
+ return {
85
+ activeDeskId,
86
+ allSections,
87
+ close: () => setOpen(false),
88
+ copyTechnicalName,
89
+ desks: scopedDesks,
90
+ highlightClassName: () =>
91
+ 'data-[ito-highlighted=true]:ring-2 data-[ito-highlighted=true]:ring-inset data-[ito-highlighted=true]:ring-violet-700',
92
+ highlighting,
93
+ inspect,
94
+ labelsVisible,
95
+ open,
96
+ regionProps: (id) => ({
97
+ 'data-ito-highlighted': sectionIds.has(id) && highlighting && selected === id,
98
+ 'data-ito-section': id,
99
+ }),
100
+ sections,
101
+ select: setSelected,
102
+ selectDesk,
103
+ setScope,
104
+ selected,
105
+ toggleHighlight: () => setHighlighting((current) => !current),
106
+ toggleLabels,
107
+ toggleOpen: () => setOpen((current) => !current),
108
+ };
109
+ }
110
+
111
+ export function validateInterfaceTopologyDesks(desks: readonly InterfaceTopologyDesk[]): void {
112
+ const deskIds = new Set<string>();
113
+ for (const desk of desks) {
114
+ if (deskIds.has(desk.id)) throw new Error(`Duplicate ITO desk id: ${desk.id}`);
115
+ deskIds.add(desk.id);
116
+ }
117
+ validateInterfaceTopologySections(desks.flatMap((desk) => desk.sections));
118
+ }
119
+
120
+ export function validateInterfaceTopologySections(sections: readonly InterfaceTopologySection[]): void {
121
+ const ids = new Set<string>();
122
+ const technicalNames = new Set<string>();
123
+
124
+ for (const section of sections) {
125
+ if (ids.has(section.id)) throw new Error(`Duplicate ITO id: ${section.id}`);
126
+ if (technicalNames.has(section.technicalName)) {
127
+ throw new Error(`Duplicate ITO technical name: ${section.technicalName}`);
128
+ }
129
+ if (!/^[a-z][a-zA-Z0-9]*\.[a-z][a-zA-Z0-9]*\.[a-z][a-zA-Z0-9]*$/.test(section.technicalName)) {
130
+ throw new Error(`ITO technical name must use section.block.control: ${section.technicalName}`);
131
+ }
132
+ ids.add(section.id);
133
+ technicalNames.add(section.technicalName);
134
+ }
135
+
136
+ for (const section of sections) {
137
+ const parentId = section.id.includes('.') ? section.id.slice(0, section.id.lastIndexOf('.')) : '';
138
+ if (parentId && !ids.has(parentId)) {
139
+ throw new Error(`ITO item ${section.id} is missing parent ${parentId}`);
140
+ }
141
+ }
142
+ }
143
+
144
+ function readLabelsVisibility() {
145
+ if (typeof window === 'undefined') return false;
146
+ try {
147
+ return window.localStorage.getItem(labelsStorageKey) === 'true';
148
+ } catch {
149
+ return false;
150
+ }
151
+ }
152
+
153
+ function persistLabelsVisibility(visible: boolean) {
154
+ try {
155
+ window.localStorage.setItem(labelsStorageKey, String(visible));
156
+ } catch {
157
+ // The session state still works when browser storage is unavailable.
158
+ }
159
+ }
160
+
161
+ function copyText(value: string) {
162
+ if (navigator.clipboard?.writeText) {
163
+ void navigator.clipboard.writeText(value).catch(() => fallbackCopy(value));
164
+ return;
165
+ }
166
+ fallbackCopy(value);
167
+ }
168
+
169
+ function fallbackCopy(value: string) {
170
+ const target = document.createElement('textarea');
171
+ target.value = value;
172
+ target.setAttribute('readonly', '');
173
+ target.className = 'fixed opacity-0';
174
+ document.body.append(target);
175
+ target.select();
176
+ document.execCommand('copy');
177
+ target.remove();
178
+ }
@@ -0,0 +1,19 @@
1
+ import * as React from 'react'
2
+
3
+ const MOBILE_BREAKPOINT = 768
4
+
5
+ export function useIsMobile() {
6
+ const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined)
7
+
8
+ React.useEffect(() => {
9
+ const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
10
+ const onChange = () => {
11
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
12
+ }
13
+ mql.addEventListener('change', onChange)
14
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
15
+ return () => mql.removeEventListener('change', onChange)
16
+ }, [])
17
+
18
+ return !!isMobile
19
+ }
@@ -0,0 +1,22 @@
1
+ import { useEffect, useRef } from 'react'
2
+
3
+ /** Follow updates only while the reader remains near the end of the scroll viewport. */
4
+ export function useScrollFollow(update: unknown, resetKey: unknown) {
5
+ const endRef = useRef<HTMLDivElement>(null)
6
+ const following = useRef(true)
7
+ useEffect(() => {
8
+ const viewport = endRef.current?.closest<HTMLElement>('[data-slot="scroll-area-viewport"]')
9
+ if (!viewport) return
10
+ following.current = true
11
+ endRef.current?.scrollIntoView({ block: 'end' })
12
+ const onScroll = () => {
13
+ following.current = viewport.scrollHeight - viewport.scrollTop - viewport.clientHeight < 80
14
+ }
15
+ viewport.addEventListener('scroll', onScroll, { passive: true })
16
+ return () => viewport.removeEventListener('scroll', onScroll)
17
+ }, [resetKey])
18
+ useEffect(() => {
19
+ if (following.current) endRef.current?.scrollIntoView({ block: 'end' })
20
+ }, [update])
21
+ return endRef
22
+ }
package/src/index.ts ADDED
@@ -0,0 +1,11 @@
1
+ export { cn } from "./lib/utils";
2
+ export { PdfProvider, type PdfDocumentSource, type PdfShareChannel } from "./providers/pdf-provider";
3
+ export { designTokens } from "./tokens/index";
4
+ export { MainWorkspace, type MainWorkspaceProps, type MdiNavigationSection } from "./layouts/main-workspace";
5
+ export { Mdi, type MdiProps } from "./layouts/mdi";
6
+ export { DocumentationWorkspace, type DocumentationWorkspaceProps } from "./layouts/documentation-workspace";
7
+ export {
8
+ WorkspaceApplicationHeader,
9
+ type WorkspaceApplicationHeaderProps,
10
+ } from "./blocks/workspace";
11
+ export { useMdiTopology, type MdiNavigationItem } from "./layouts/main-workspace";
@@ -0,0 +1,65 @@
1
+ # UI Layouts
2
+
3
+ Layouts are shared application frames. They own reusable navigation chrome and content boundaries,
4
+ but they do not own product routes, business entities, persistence, or workflows.
5
+
6
+ ## Layout boundaries
7
+
8
+ - `main-workspace` is the shared shell for desktop application workspaces.
9
+ - `mdi` is a plain centered-label surface for layout previews and empty states.
10
+ - `documentation-workspace` applies Docs defaults to `main-workspace`.
11
+ - `agent-workspace` adds activity rails inside `main-workspace`.
12
+ - `site-header`, `ecommerce-header`, and `blog-header` are standalone page headers. Do not compose
13
+ them into `main-workspace`.
14
+
15
+ Each layout folder exports one public component through `index.ts`. Keep its internal parts in the
16
+ same folder. Name internal parts after their visible job, such as `ecommerce-search` or
17
+ `ecommerce-navigation`.
18
+
19
+ - `main-workspace` provides the CODEXSUN multiple-document application frame. Its public parts
20
+ include the top menu, application switcher, profile menu, shadcn sidebar, status bar, and feature
21
+ settings screen. Pass application-owned navigation data and workspace content through the public
22
+ layout contract. Applications can provide a controlled search value and change callback; the
23
+ layout renders the field but does not own search state or result behavior.
24
+
25
+ When an application omits `children`, MDI leaves the workspace canvas empty.
26
+
27
+ - `agent-workspace` provides a fixed Primary Activity Rail, a center Workspace Canvas, and a fixed
28
+ Secondary Utility Rail. Applications supply typed icon items and actions.
29
+
30
+ Pass `agentWorkspace` to `MainWorkspace` to connect the Agent Workspace inside the shared shell. The
31
+ built-in feature settings show one visibility switch for each activity rail. MDI places the Primary
32
+ Activity Rail before the default sidebar and keeps the utility rail rightmost.
33
+
34
+ Applications can replace the standard primary action and navigation area with `sidebarContent`. They
35
+ can replace the footer with a React node or pass `null` to remove it. An omitted `sidebarFooter`
36
+ keeps the shared Feature settings footer. The MDI shell continues to own sidebar sizing, collapse
37
+ behavior, and the rail toggle.
38
+
39
+ An application can map a parent ITO group to the complete area below the command bar with
40
+ `deskRegionId`. Its workspace and sidebar regions can then be registered as child components of that
41
+ desk.
42
+
43
+ Feature visibility is stored per `applicationId`. The shared feature settings screen can toggle the
44
+ top menu, notifications, application switcher, profile menu, and bottom status bar. Product routes
45
+ and workflows remain app-owned.
46
+
47
+ Applications can pass `statusEnd` to place compact metadata at the right edge of the status bar. The
48
+ application owns the content and its text style.
49
+
50
+ `MainWorkspace` binds `@devxcrew/react-ui/theme` for every application. The appearance control changes
51
+ light, dark, or system mode and the shared accent composition. Applications must use semantic tokens
52
+ instead of storing their own shell theme.
53
+
54
+ `MainWorkspace` also sets the browser document title from `applicationName`. Shared templates and
55
+ application routes must not replace this title.
56
+
57
+ Applications import layouts through their public `@devxcrew/react-ui/layouts/*` entry points.
58
+
59
+ The default MDI application catalog links Platform, UI, Docs, and Zetro. It uses the documented
60
+ local ports when the current host is localhost or `127.0.0.1`. Applications can provide `apps` to
61
+ replace these destinations for a deployed environment.
62
+
63
+ The ITO inspector shows a shadcn Select only when the current page supplies more than one desk.
64
+ `showMdiOverview` adds the shared MDI Overview desk. A normal application desk contains only
65
+ app-owned regions and does not show the desk selector.
@@ -0,0 +1,101 @@
1
+ import { cn } from '@devxcrew/react-ui/lib/utils'
2
+ import { Button } from '@devxcrew/react-ui/components/button'
3
+ import {
4
+ Tooltip,
5
+ TooltipContent,
6
+ TooltipProvider,
7
+ TooltipTrigger,
8
+ } from '@devxcrew/react-ui/components/tooltip'
9
+
10
+ import type { AgentWorkspaceRail, AgentWorkspaceRailItem } from './agent-workspace.types'
11
+
12
+ type AgentActivityRailProps = {
13
+ rail: AgentWorkspaceRail
14
+ side: 'left' | 'right'
15
+ visible: boolean
16
+ }
17
+
18
+ export function AgentActivityRail({ rail, side, visible }: AgentActivityRailProps) {
19
+ return (
20
+ <TooltipProvider>
21
+ <aside
22
+ aria-hidden={!visible}
23
+ aria-label={rail.label}
24
+ className={cn(
25
+ 'relative z-20 h-full shrink-0 overflow-hidden bg-sidebar transition-[width,opacity,border-color] duration-200 motion-reduce:transition-none',
26
+ visible
27
+ ? cn('w-14 border-border opacity-100', side === 'left' ? 'border-r' : 'border-l')
28
+ : 'pointer-events-none w-0 border-0 opacity-0',
29
+ )}
30
+ >
31
+ {visible ? (
32
+ <div className="flex h-full w-14 flex-col items-center gap-2 px-2 py-3">
33
+ <RailItems items={rail.items} side={side} />
34
+ {rail.footerItems?.length ? (
35
+ <div className="flex min-h-0 flex-1 flex-col justify-end">
36
+ <RailItems items={rail.footerItems} side={side} />
37
+ </div>
38
+ ) : null}
39
+ </div>
40
+ ) : null}
41
+ </aside>
42
+ </TooltipProvider>
43
+ )
44
+ }
45
+
46
+ function RailItems({
47
+ items,
48
+ side,
49
+ }: {
50
+ items: readonly AgentWorkspaceRailItem[]
51
+ side: 'left' | 'right'
52
+ }) {
53
+ return (
54
+ <nav
55
+ className="flex flex-col gap-2"
56
+ aria-label={side === 'left' ? 'Primary tools' : 'Secondary tools'}
57
+ >
58
+ {items.map((item) => (
59
+ <RailAction item={item} key={item.id} side={side} />
60
+ ))}
61
+ </nav>
62
+ )
63
+ }
64
+
65
+ function RailAction({ item, side }: { item: AgentWorkspaceRailItem; side: 'left' | 'right' }) {
66
+ const Icon = item.icon
67
+
68
+ return (
69
+ <Tooltip>
70
+ <TooltipTrigger
71
+ render={
72
+ <Button
73
+ aria-label={item.label}
74
+ aria-current={item.active ? 'page' : undefined}
75
+ className={cn(
76
+ 'relative hover:translate-y-0 active:translate-y-0 motion-reduce:transition-none',
77
+ item.active &&
78
+ 'bg-sidebar-accent text-sidebar-accent-foreground ring-1 ring-sidebar-border before:absolute before:inset-y-2 before:-left-1.5 before:w-0.5 before:rounded-full before:bg-primary hover:bg-sidebar-accent',
79
+ )}
80
+ disabled={item.disabled}
81
+ size="icon"
82
+ variant={item.active ? 'secondary' : 'ghost'}
83
+ onClick={item.onSelect}
84
+ />
85
+ }
86
+ >
87
+ <Icon />
88
+ {item.badge !== undefined ? <RailBadge value={item.badge} /> : null}
89
+ </TooltipTrigger>
90
+ <TooltipContent side={side === 'left' ? 'right' : 'left'}>{item.label}</TooltipContent>
91
+ </Tooltip>
92
+ )
93
+ }
94
+
95
+ function RailBadge({ value }: { value: number | string }) {
96
+ return (
97
+ <span className="absolute top-0.5 right-0.5 grid min-h-3.5 min-w-3.5 place-items-center rounded-full bg-primary px-1 text-[10px] leading-none font-semibold text-primary-foreground">
98
+ {value}
99
+ </span>
100
+ )
101
+ }
@@ -0,0 +1,27 @@
1
+ import { cn } from '@devxcrew/react-ui/lib/utils'
2
+
3
+ import { AgentActivityRail } from './agent-activity-rail'
4
+ import type { AgentWorkspaceProps } from './agent-workspace.types'
5
+
6
+ export function AgentWorkspace({
7
+ canvasClassName,
8
+ children,
9
+ className,
10
+ primaryRail,
11
+ secondaryRail,
12
+ showPrimaryRail = true,
13
+ showSecondaryRail = true,
14
+ }: AgentWorkspaceProps) {
15
+ return (
16
+ <section
17
+ aria-label="Agent workspace"
18
+ className={cn('flex size-full min-h-0 min-w-0 overflow-hidden bg-background', className)}
19
+ >
20
+ <AgentActivityRail rail={primaryRail} side="left" visible={showPrimaryRail} />
21
+ <div className={cn('relative min-h-0 min-w-0 flex-1 overflow-hidden', canvasClassName)}>
22
+ {children}
23
+ </div>
24
+ <AgentActivityRail rail={secondaryRail} side="right" visible={showSecondaryRail} />
25
+ </section>
26
+ )
27
+ }
@@ -0,0 +1,28 @@
1
+ import type { LucideIcon } from 'lucide-react'
2
+ import type { ReactNode } from 'react'
3
+
4
+ export type AgentWorkspaceRailItem = {
5
+ active?: boolean
6
+ badge?: number | string
7
+ disabled?: boolean
8
+ icon: LucideIcon
9
+ id: string
10
+ label: string
11
+ onSelect?: () => void
12
+ }
13
+
14
+ export type AgentWorkspaceRail = {
15
+ footerItems?: readonly AgentWorkspaceRailItem[]
16
+ items: readonly AgentWorkspaceRailItem[]
17
+ label: string
18
+ }
19
+
20
+ export type AgentWorkspaceProps = {
21
+ canvasClassName?: string
22
+ children?: ReactNode
23
+ className?: string
24
+ primaryRail: AgentWorkspaceRail
25
+ secondaryRail: AgentWorkspaceRail
26
+ showPrimaryRail?: boolean
27
+ showSecondaryRail?: boolean
28
+ }
@@ -0,0 +1,7 @@
1
+ export { AgentWorkspace } from './agent-workspace'
2
+ export { AgentActivityRail } from './agent-activity-rail'
3
+ export type {
4
+ AgentWorkspaceProps,
5
+ AgentWorkspaceRail,
6
+ AgentWorkspaceRailItem,
7
+ } from './agent-workspace.types'
@@ -0,0 +1,25 @@
1
+ import type { ReactNode } from 'react'
2
+
3
+ export type BlogTopicTag = {
4
+ readonly active?: boolean
5
+ readonly href: string
6
+ readonly id: string
7
+ readonly label: string
8
+ readonly postCount?: number
9
+ }
10
+
11
+ export type BlogHeaderProps = {
12
+ readonly backToStoreHref?: string
13
+ readonly backToStoreLabel?: string
14
+ readonly brand: {
15
+ readonly badge?: string
16
+ readonly href?: string
17
+ readonly logo?: ReactNode
18
+ readonly title: string
19
+ }
20
+ readonly className?: string
21
+ readonly onNewsletterClick?: () => void
22
+ readonly onSearch?: (query: string) => void
23
+ readonly readingProgress?: number
24
+ readonly topics?: readonly BlogTopicTag[]
25
+ }
@@ -0,0 +1,172 @@
1
+ import { useState, useEffect } from 'react'
2
+ import { ArrowLeftIcon, BookOpenIcon, MailIcon, SearchIcon, XIcon } from 'lucide-react'
3
+ import { cn } from '../../lib/utils'
4
+ import { Button } from '../../components/button'
5
+ import { Badge } from '../../components/badge'
6
+ import type { BlogHeaderProps } from './blog-header-types'
7
+
8
+ export function BlogHeader({
9
+ backToStoreHref = '/',
10
+ backToStoreLabel = 'Back to Store',
11
+ brand,
12
+ className,
13
+ onNewsletterClick,
14
+ onSearch,
15
+ readingProgress: explicitProgress,
16
+ topics = [
17
+ { active: true, href: '#all', id: 'all', label: 'All Stories' },
18
+ { href: '#engineering', id: 'engineering', label: 'Engineering' },
19
+ { href: '#product-design', id: 'product-design', label: 'Product Design' },
20
+ { href: '#architecture', id: 'architecture', label: 'Architecture' },
21
+ { href: '#guides', id: 'guides', label: 'Guides & Case Studies' },
22
+ ],
23
+ }: BlogHeaderProps) {
24
+ const [scrollProgress, setScrollProgress] = useState(0)
25
+ const [searchQuery, setSearchQuery] = useState('')
26
+ const [showSearch, setShowSearch] = useState(false)
27
+
28
+ useEffect(() => {
29
+ if (explicitProgress !== undefined) return
30
+
31
+ const handleScroll = () => {
32
+ const totalHeight = document.documentElement.scrollHeight - window.innerHeight
33
+ if (totalHeight <= 0) {
34
+ setScrollProgress(0)
35
+ return
36
+ }
37
+ const currentProgress = (window.scrollY / totalHeight) * 100
38
+ setScrollProgress(Math.min(100, Math.max(0, currentProgress)))
39
+ }
40
+
41
+ window.addEventListener('scroll', handleScroll, { passive: true })
42
+ return () => window.removeEventListener('scroll', handleScroll)
43
+ }, [explicitProgress])
44
+
45
+ const progress = explicitProgress !== undefined ? explicitProgress : scrollProgress
46
+
47
+ return (
48
+ <header
49
+ className={cn(
50
+ 'sticky top-0 z-40 w-full border-b border-border/80 bg-background/95 backdrop-blur-md',
51
+ className,
52
+ )}
53
+ >
54
+ {/* 1. Scroll / Reading Progress Line */}
55
+ <div className="absolute top-0 left-0 right-0 h-1 bg-muted/40">
56
+ <div
57
+ className="h-full bg-primary transition-all duration-150"
58
+ style={{ width: `${progress}%` }}
59
+ />
60
+ </div>
61
+
62
+ {/* 2. Top Bridge & Brand Bar */}
63
+ <div className="mx-auto flex max-w-7xl items-center justify-between gap-4 px-4 py-3">
64
+ {/* Back to Store link + Brand */}
65
+ <div className="flex items-center gap-4">
66
+ <a
67
+ href={backToStoreHref}
68
+ className="flex items-center gap-1.5 text-xs font-semibold text-muted-foreground hover:text-foreground transition-colors"
69
+ >
70
+ <ArrowLeftIcon className="size-3.5" />
71
+ <span>{backToStoreLabel}</span>
72
+ </a>
73
+
74
+ <div className="h-4 w-px bg-border/80 hidden sm:block" />
75
+
76
+ <a href={brand.href ?? '/blog'} className="flex items-center gap-2">
77
+ {brand.logo ?? <BookOpenIcon className="size-5 text-primary" />}
78
+ <div className="flex items-baseline gap-2">
79
+ <span className="text-lg font-bold tracking-tight text-foreground">
80
+ {brand.title}
81
+ </span>
82
+ {brand.badge && (
83
+ <Badge variant="secondary" className="text-[10px] font-semibold py-0">
84
+ {brand.badge}
85
+ </Badge>
86
+ )}
87
+ </div>
88
+ </a>
89
+ </div>
90
+
91
+ {/* Search & Newsletter CTA */}
92
+ <div className="flex items-center gap-2">
93
+ {showSearch ? (
94
+ <div className="flex items-center rounded-lg border border-input bg-muted/30 px-2.5 py-1">
95
+ <SearchIcon className="size-3.5 text-muted-foreground mr-1.5" />
96
+ <input
97
+ type="text"
98
+ value={searchQuery}
99
+ onChange={(e) => setSearchQuery(e.target.value)}
100
+ onKeyDown={(e) => {
101
+ if (e.key === 'Enter') onSearch?.(searchQuery)
102
+ }}
103
+ placeholder="Search articles..."
104
+ className="w-36 sm:w-48 bg-transparent text-xs placeholder:text-muted-foreground focus:outline-none"
105
+ autoFocus
106
+ />
107
+ <button
108
+ type="button"
109
+ onClick={() => setShowSearch(false)}
110
+ className="text-muted-foreground hover:text-foreground ml-1"
111
+ >
112
+ <XIcon className="size-3.5" />
113
+ </button>
114
+ </div>
115
+ ) : (
116
+ <Button
117
+ size="sm"
118
+ variant="ghost"
119
+ className="h-8 gap-1.5 text-xs text-muted-foreground hover:text-foreground"
120
+ onClick={() => setShowSearch(true)}
121
+ >
122
+ <SearchIcon className="size-4" />
123
+ <span className="hidden sm:inline">Search</span>
124
+ </Button>
125
+ )}
126
+
127
+ <Button
128
+ size="sm"
129
+ variant="default"
130
+ className="h-8 gap-1.5 text-xs font-medium"
131
+ onClick={onNewsletterClick}
132
+ >
133
+ <MailIcon className="size-3.5" />
134
+ <span>Subscribe</span>
135
+ </Button>
136
+ </div>
137
+ </div>
138
+
139
+ {/* 3. Topics Strip */}
140
+ {topics.length > 0 && (
141
+ <div className="border-t border-border/40 bg-muted/15 px-4 py-1.5">
142
+ <div className="mx-auto flex max-w-7xl items-center gap-1.5 overflow-x-auto no-scrollbar">
143
+ {topics.map((t) => (
144
+ <a
145
+ key={t.id}
146
+ href={t.href}
147
+ className={cn(
148
+ 'flex items-center gap-1 rounded-full px-3 py-1 text-xs font-medium transition-all',
149
+ t.active
150
+ ? 'bg-primary text-primary-foreground font-semibold shadow-xs'
151
+ : 'text-muted-foreground hover:bg-muted hover:text-foreground',
152
+ )}
153
+ >
154
+ <span>{t.label}</span>
155
+ {t.postCount !== undefined && (
156
+ <span
157
+ className={cn(
158
+ 'text-[10px] opacity-75',
159
+ t.active ? 'text-primary-foreground' : 'text-muted-foreground',
160
+ )}
161
+ >
162
+ ({t.postCount})
163
+ </span>
164
+ )}
165
+ </a>
166
+ ))}
167
+ </div>
168
+ </div>
169
+ )}
170
+ </header>
171
+ )
172
+ }
@@ -0,0 +1,2 @@
1
+ export * from './blog-header'
2
+ export * from './blog-header-types'
@@ -0,0 +1,8 @@
1
+ # Documentation Workspace
2
+
3
+ `DocumentationWorkspace` is the shared MDI shell for documentation and knowledge applications. It
4
+ supplies Docs identity, search, sidebar persistence, status, and workspace defaults while the owning
5
+ application supplies navigation, content, settings, topology, and data loading.
6
+
7
+ Import it from `@devxcrew/react-ui/layouts/documentation-workspace`. Do not put document persistence,
8
+ repository discovery, editing, or application routes in this package layout.