@digithings-ai/ui 0.0.0-stage → 0.1.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 (424) hide show
  1. package/README.md +48 -2
  2. package/package.json +143 -4
  3. package/src/components/NavShell.tsx +776 -0
  4. package/src/components/SocialRow.tsx +87 -0
  5. package/src/components/Spinner.tsx +27 -0
  6. package/src/components/StackLogo.tsx +40 -0
  7. package/src/components/Terminal.tsx +110 -0
  8. package/src/components/TerminalManifest.tsx +212 -0
  9. package/src/components/ThemeProvider.tsx +271 -0
  10. package/src/components/account/AuthCard.tsx +309 -0
  11. package/src/components/assistant-ui/elements/mermaid-diagram.aui.tsx +63 -0
  12. package/src/components/assistant-ui/elements/mermaid-diagram.tsx +340 -0
  13. package/src/components/brand/BrandWordmark.tsx +32 -0
  14. package/src/components/changelog/ReleaseRail.tsx +81 -0
  15. package/src/components/changelog/index.ts +1 -0
  16. package/src/components/chat/ChatCodeBlock.tsx +67 -0
  17. package/src/components/chat/ChatMarkdown.tsx +66 -0
  18. package/src/components/chat/ChatMarkdownSource.tsx +203 -0
  19. package/src/components/chat/ChatMermaidBlock.tsx +202 -0
  20. package/src/components/chat/ChatMessage.tsx +77 -0
  21. package/src/components/chat/ChatPlayback.tsx +422 -0
  22. package/src/components/chat/ChatStreamCursor.tsx +25 -0
  23. package/src/components/chat/ChatThinking.tsx +113 -0
  24. package/src/components/chat/ChatToolCall.tsx +190 -0
  25. package/src/components/chat/ChatTranscript.tsx +60 -0
  26. package/src/components/chat/ChatWidgetFrame.tsx +107 -0
  27. package/src/components/chat/DigichatBootLoader.tsx +683 -0
  28. package/src/components/chat/DigichatLauncher.tsx +361 -0
  29. package/src/components/chat/DigichatThread.tsx +118 -0
  30. package/src/components/chat/DigichatThreadList.tsx +54 -0
  31. package/src/components/chat/DigichatWordmark.tsx +24 -0
  32. package/src/components/chat/DotMatrix.tsx +650 -0
  33. package/src/components/chat/boot-lab.tsx +766 -0
  34. package/src/components/chat/boot-signal.ts +144 -0
  35. package/src/components/chat/chat-surfaces.ts +52 -0
  36. package/src/components/chat/connection-status.ts +40 -0
  37. package/src/components/chat/digichat-wordmark.ts +355 -0
  38. package/src/components/chat/disclosure-chevron.tsx +49 -0
  39. package/src/components/chat/followable-href.ts +9 -0
  40. package/src/components/chat/gallery-thread/action-icons.tsx +25 -0
  41. package/src/components/chat/gallery-thread/attachment.aui.tsx +262 -0
  42. package/src/components/chat/gallery-thread/block-caret.tsx +439 -0
  43. package/src/components/chat/gallery-thread/cn.ts +1 -0
  44. package/src/components/chat/gallery-thread/composer-keyboard.ts +106 -0
  45. package/src/components/chat/gallery-thread/composer-trigger-popover.aui.tsx +235 -0
  46. package/src/components/chat/gallery-thread/css.d.ts +6 -0
  47. package/src/components/chat/gallery-thread/file.tsx +238 -0
  48. package/src/components/chat/gallery-thread/follow-up-suggestions.aui.tsx +92 -0
  49. package/src/components/chat/gallery-thread/format-json-dump.ts +112 -0
  50. package/src/components/chat/gallery-thread/hooks/use-attachment-src.ts +46 -0
  51. package/src/components/chat/gallery-thread/hooks/use-copy-to-clipboard.ts +9 -0
  52. package/src/components/chat/gallery-thread/image.tsx +519 -0
  53. package/src/components/chat/gallery-thread/markdown-text.tsx +278 -0
  54. package/src/components/chat/gallery-thread/message-error.aui.tsx +105 -0
  55. package/src/components/chat/gallery-thread/message-timing.aui.tsx +143 -0
  56. package/src/components/chat/gallery-thread/private-work-step.tsx +188 -0
  57. package/src/components/chat/gallery-thread/reasoning.tsx +332 -0
  58. package/src/components/chat/gallery-thread/scroll-mode.ts +174 -0
  59. package/src/components/chat/gallery-thread/source.tsx +76 -0
  60. package/src/components/chat/gallery-thread/thread.aui.tsx +1619 -0
  61. package/src/components/chat/gallery-thread/tooltip-icon-button.tsx +53 -0
  62. package/src/components/chat/gallery-thread/typed-welcome-copy.tsx +18 -0
  63. package/src/components/chat/gallery-thread/ui/tooltip.tsx +22 -0
  64. package/src/components/chat/gallery-thread/work-chain-summary.tsx +48 -0
  65. package/src/components/chat/gallery-thread/work-chain.tsx +110 -0
  66. package/src/components/chat/gallery-thread/work-status.ts +66 -0
  67. package/src/components/chat/overlay-scrollbar.tsx +86 -0
  68. package/src/components/chat/sanitize-mermaid-svg.ts +38 -0
  69. package/src/components/chat/skins/SOURCE.md +36 -0
  70. package/src/components/chat/skins/base/attachment.aui.tsx +287 -0
  71. package/src/components/chat/skins/base/aui-default/README.md +45 -0
  72. package/src/components/chat/skins/base/aui-default/collapsible-root.tsx +19 -0
  73. package/src/components/chat/skins/base/aui-default/collapsible.tsx +21 -0
  74. package/src/components/chat/skins/base/aui-default/reasoning.aui.tsx +120 -0
  75. package/src/components/chat/skins/base/aui-default/reasoning.tsx +331 -0
  76. package/src/components/chat/skins/base/aui-default/surfaces.ts +7 -0
  77. package/src/components/chat/skins/base/aui-default/tool-fallback.aui.tsx +772 -0
  78. package/src/components/chat/skins/base/aui-default/tool-group.aui.tsx +241 -0
  79. package/src/components/chat/skins/base/docs-model-options.ts +6 -0
  80. package/src/components/chat/skins/base/elements/assistant-trace.aui.tsx +169 -0
  81. package/src/components/chat/skins/base/elements/composer-trigger-popover.aui.tsx +271 -0
  82. package/src/components/chat/skins/base/elements/directive-text.aui.tsx +29 -0
  83. package/src/components/chat/skins/base/elements/directive-text.tsx +76 -0
  84. package/src/components/chat/skins/base/elements/message-timing.aui.tsx +104 -0
  85. package/src/components/chat/skins/base/elements/model-selector.aui.tsx +140 -0
  86. package/src/components/chat/skins/base/elements/model-selector.tsx +720 -0
  87. package/src/components/chat/skins/base/elements/quote.aui.tsx +290 -0
  88. package/src/components/chat/skins/base/elements/work-chain.tsx +100 -0
  89. package/src/components/chat/skins/base/markdown-text.tsx +294 -0
  90. package/src/components/chat/skins/base/model.ts +1 -0
  91. package/src/components/chat/skins/base/thread.tsx +1050 -0
  92. package/src/components/chat/skins/base/tooltip-icon-button.tsx +56 -0
  93. package/src/components/chat/skins/base/ui/avatar.tsx +115 -0
  94. package/src/components/chat/skins/base/ui/badge.tsx +51 -0
  95. package/src/components/chat/skins/base/ui/button.tsx +67 -0
  96. package/src/components/chat/skins/base/ui/command.tsx +185 -0
  97. package/src/components/chat/skins/base/ui/dialog.tsx +168 -0
  98. package/src/components/chat/skins/base/ui/dot-matrix.tsx +267 -0
  99. package/src/components/chat/skins/base/ui/popover.tsx +97 -0
  100. package/src/components/chat/skins/base/ui/skeleton.tsx +20 -0
  101. package/src/components/chat/skins/base/ui/textarea.tsx +24 -0
  102. package/src/components/chat/skins/base/ui/tooltip.tsx +73 -0
  103. package/src/components/chat/skins/base-assistant-ui/config-provider.tsx +90 -0
  104. package/src/components/chat/skins/base-assistant-ui/defaults.ts +419 -0
  105. package/src/components/chat/skins/base-assistant-ui/index.tsx +44 -0
  106. package/src/components/chat/skins/chatgpt.tsx +448 -0
  107. package/src/components/chat/skins/claude.tsx +380 -0
  108. package/src/components/chat/skins/digichat-composer-toolbar.tsx +151 -0
  109. package/src/components/chat/skins/digichat-context-meter-shares.ts +158 -0
  110. package/src/components/chat/skins/digichat-context-meter.tsx +243 -0
  111. package/src/components/chat/skins/digichat-meter-tone.ts +26 -0
  112. package/src/components/chat/skins/digichat-usage-meter.tsx +103 -0
  113. package/src/components/chat/skins/digichat.tsx +144 -0
  114. package/src/components/chat/skins/expo-react-native.tsx +29 -0
  115. package/src/components/chat/skins/gemini.tsx +322 -0
  116. package/src/components/chat/skins/grok-icon.tsx +34 -0
  117. package/src/components/chat/skins/grok.tsx +369 -0
  118. package/src/components/chat/skins/index.ts +46 -0
  119. package/src/components/chat/skins/perplexity.tsx +462 -0
  120. package/src/components/chat/skins/product-page-assistant/assistant-modal.tsx +73 -0
  121. package/src/components/chat/skins/product-page-assistant/index.tsx +19 -0
  122. package/src/components/chat/skins/product-page-assistant/product-dashboard.tsx +164 -0
  123. package/src/components/chat/skins/react-ink.tsx +97 -0
  124. package/src/components/chat/skins/thread-skin-host-contract.ts +102 -0
  125. package/src/components/chat/skins/thread-skin-view.tsx +156 -0
  126. package/src/components/chat/skins/thread-skins.ts +143 -0
  127. package/src/components/chat/skins/tooltip-icon-button.tsx +35 -0
  128. package/src/components/chat/skins/ui/dropdown-menu.tsx +272 -0
  129. package/src/components/chat/skins/webpage-assistant/assistant-config.ts +238 -0
  130. package/src/components/chat/skins/webpage-assistant/assistant-modal.tsx +78 -0
  131. package/src/components/chat/skins/webpage-assistant/config-provider.tsx +132 -0
  132. package/src/components/chat/skins/webpage-assistant/docs-app.tsx +106 -0
  133. package/src/components/chat/skins/webpage-assistant/docs-article.tsx +109 -0
  134. package/src/components/chat/skins/webpage-assistant/docs-assistant.tsx +73 -0
  135. package/src/components/chat/skins/webpage-assistant/docs-nav.tsx +81 -0
  136. package/src/components/chat/skins/webpage-assistant/host-ui.ts +79 -0
  137. package/src/components/chat/skins/webpage-assistant/index.tsx +12 -0
  138. package/src/components/chat/skins/webpage-assistant/markdown-page.tsx +11 -0
  139. package/src/components/chat/skins/webpage-assistant/pages.ts +9 -0
  140. package/src/components/chat/skins/webpage-assistant/types.ts +178 -0
  141. package/src/components/chat/skins/webpage-assistant/use-mobile.ts +19 -0
  142. package/src/components/chat/stock/SOURCE.md +90 -0
  143. package/src/components/chat/stock/artifacts-panel.tsx +305 -0
  144. package/src/components/chat/stock/attachment.aui.tsx +280 -0
  145. package/src/components/chat/stock/chat-notice.tsx +143 -0
  146. package/src/components/chat/stock/chat-private-mode.tsx +64 -0
  147. package/src/components/chat/stock/deploy-ui-context.tsx +89 -0
  148. package/src/components/chat/stock/embed-chat-prefs.tsx +471 -0
  149. package/src/components/chat/stock/file.tsx +265 -0
  150. package/src/components/chat/stock/follow-up-suggestions.aui.tsx +83 -0
  151. package/src/components/chat/stock/image.tsx +531 -0
  152. package/src/components/chat/stock/index.ts +129 -0
  153. package/src/components/chat/stock/json-view.tsx +140 -0
  154. package/src/components/chat/stock/markdown-text.tsx +319 -0
  155. package/src/components/chat/stock/message-error.aui.tsx +96 -0
  156. package/src/components/chat/stock/message-export-menu.tsx +156 -0
  157. package/src/components/chat/stock/palette-row-preview.tsx +26 -0
  158. package/src/components/chat/stock/reasoning.aui.tsx +120 -0
  159. package/src/components/chat/stock/reasoning.tsx +335 -0
  160. package/src/components/chat/stock/skin-chrome.tsx +81 -0
  161. package/src/components/chat/stock/skin-credit.tsx +63 -0
  162. package/src/components/chat/stock/skin-palette-bridge.tsx +235 -0
  163. package/src/components/chat/stock/skin-palette-context.ts +16 -0
  164. package/src/components/chat/stock/skin-runtime.tsx +52 -0
  165. package/src/components/chat/stock/skin-slash-palette.tsx +393 -0
  166. package/src/components/chat/stock/skin-types.ts +217 -0
  167. package/src/components/chat/stock/stock-chat-prefs-host.tsx +501 -0
  168. package/src/components/chat/stock/stock-send-gate.tsx +94 -0
  169. package/src/components/chat/stock/stock-shape.tsx +39 -0
  170. package/src/components/chat/stock/thread.aui.tsx +645 -0
  171. package/src/components/chat/stock/tool-display.ts +6 -0
  172. package/src/components/chat/stock/tool-fallback.aui.tsx +866 -0
  173. package/src/components/chat/stock/tool-group.aui.tsx +244 -0
  174. package/src/components/chat/stock/tool-surface.ts +120 -0
  175. package/src/components/chat/stock/tooltip-icon-button.tsx +49 -0
  176. package/src/components/chat/stock/ui/avatar.tsx +108 -0
  177. package/src/components/chat/stock/ui/button.tsx +81 -0
  178. package/src/components/chat/stock/ui/collapsible.tsx +21 -0
  179. package/src/components/chat/stock/ui/dialog.tsx +163 -0
  180. package/src/components/chat/stock/ui/skeleton.tsx +13 -0
  181. package/src/components/chat/stock/ui/textarea.tsx +20 -0
  182. package/src/components/chat/stock/ui/tooltip.tsx +68 -0
  183. package/src/components/chat/stock/use-attachment-src.ts +42 -0
  184. package/src/components/chat/stock/use-copy-to-clipboard.ts +29 -0
  185. package/src/components/chat/strip-model-boilerplate.ts +42 -0
  186. package/src/components/chat/suggestion-cycle.tsx +230 -0
  187. package/src/components/chat/suggestion-scroll-row.tsx +81 -0
  188. package/src/components/chat/trace-grouping/chain-group-by.ts +44 -0
  189. package/src/components/chat/trace-grouping/chain-state.ts +141 -0
  190. package/src/components/chat/trace-grouping/index.ts +32 -0
  191. package/src/components/chat/trace-grouping/tool-row.ts +74 -0
  192. package/src/components/chat/trace-grouping/use-deploy-work-disclosure.ts +35 -0
  193. package/src/components/chat/trace-grouping/use-work-chain.ts +103 -0
  194. package/src/components/chat/trace-i18n/core.ts +48 -0
  195. package/src/components/chat/trace-i18n/index.tsx +23 -0
  196. package/src/components/chat/trace-i18n/locales/en.ts +24 -0
  197. package/src/components/chat/trace-i18n/locales/fr.ts +28 -0
  198. package/src/components/chat/trace-i18n/types.ts +35 -0
  199. package/src/components/chat/trace-spacing.ts +249 -0
  200. package/src/components/chat/transcript/activity-view.ts +375 -0
  201. package/src/components/chat/transcript/chat-export.ts +133 -0
  202. package/src/components/chat/transcript/index.ts +10 -0
  203. package/src/components/chat/transcript/save-file.ts +209 -0
  204. package/src/components/chat/transcript/transcript-markdown.ts +428 -0
  205. package/src/components/chat/transcript/types.ts +81 -0
  206. package/src/components/chat/turn-outcome.ts +52 -0
  207. package/src/components/chat/use-warmup-phase.ts +18 -0
  208. package/src/components/chat/work-status-lead.tsx +49 -0
  209. package/src/components/chrome.tsx +264 -0
  210. package/src/components/command-palette/CommandPalette.tsx +404 -0
  211. package/src/components/command-palette/index.ts +6 -0
  212. package/src/components/contact/ContactMailto.tsx +112 -0
  213. package/src/components/contact/index.ts +5 -0
  214. package/src/components/conviction/ConvictionMeter.tsx +40 -0
  215. package/src/components/conviction/SignedConvictionBadge.tsx +26 -0
  216. package/src/components/conviction/index.ts +7 -0
  217. package/src/components/data-layout/BentoGrid.tsx +52 -0
  218. package/src/components/data-layout/CardRail.tsx +150 -0
  219. package/src/components/data-layout/DotMatrixStat.tsx +65 -0
  220. package/src/components/data-layout/FeatureCell.tsx +80 -0
  221. package/src/components/data-layout/MediaFrame.tsx +125 -0
  222. package/src/components/data-layout/ModuleGrid.tsx +735 -0
  223. package/src/components/data-layout/Odometer.tsx +149 -0
  224. package/src/components/data-layout/ProductFrame.tsx +79 -0
  225. package/src/components/data-layout/TestimonialWall.tsx +139 -0
  226. package/src/components/data-layout/index.ts +19 -0
  227. package/src/components/data-layout/treemap.ts +368 -0
  228. package/src/components/deck/DeckStack.tsx +243 -0
  229. package/src/components/desk/DataTable.tsx +373 -0
  230. package/src/components/desk/KpiGrid.tsx +80 -0
  231. package/src/components/desk/Sparkline.tsx +63 -0
  232. package/src/components/desk/StateBlock.tsx +132 -0
  233. package/src/components/desk/index.ts +18 -0
  234. package/src/components/diagrams/ArchitectureDiagram.tsx +350 -0
  235. package/src/components/diagrams/ArchitectureTour.tsx +912 -0
  236. package/src/components/diagrams/TerminalSchematic.tsx +192 -0
  237. package/src/components/diagrams/architecture-svg.tsx +384 -0
  238. package/src/components/diagrams/index.ts +30 -0
  239. package/src/components/diagrams/mermaid-theme.ts +115 -0
  240. package/src/components/diagrams/tour-camera.ts +171 -0
  241. package/src/components/docs/CodeTabs.tsx +90 -0
  242. package/src/components/docs/CopyCommand.tsx +151 -0
  243. package/src/components/docs/DocsLayout.tsx +197 -0
  244. package/src/components/docs/DocsSearch.tsx +97 -0
  245. package/src/components/docs/Endpoint.tsx +135 -0
  246. package/src/components/document/DocumentFrame.tsx +43 -0
  247. package/src/components/document/GlyphList.tsx +51 -0
  248. package/src/components/document/PageTitle.tsx +44 -0
  249. package/src/components/document/Prose.tsx +38 -0
  250. package/src/components/document/Section.tsx +55 -0
  251. package/src/components/document/index.ts +13 -0
  252. package/src/components/effects-chrome/AnnouncementBar.tsx +88 -0
  253. package/src/components/effects-chrome/HorizontalScrollTrack.tsx +461 -0
  254. package/src/components/effects-chrome/Pipeline.tsx +226 -0
  255. package/src/components/effects-chrome/RotatingPrompts.tsx +116 -0
  256. package/src/components/effects-chrome/StackingPanels.tsx +101 -0
  257. package/src/components/effects-chrome/TabStrip.tsx +232 -0
  258. package/src/components/effects-chrome/ToastStack.tsx +201 -0
  259. package/src/components/effects-chrome/horizontal-track-core.ts +70 -0
  260. package/src/components/effects-chrome/index.ts +35 -0
  261. package/src/components/emblems.tsx +188 -0
  262. package/src/components/faviconTheme.ts +23 -0
  263. package/src/components/figure/Figure.tsx +38 -0
  264. package/src/components/figure/index.ts +2 -0
  265. package/src/components/finance-charts/DrawdownPlot.tsx +76 -0
  266. package/src/components/finance-charts/EquityCurve.tsx +71 -0
  267. package/src/components/finance-charts/PriceChart.tsx +119 -0
  268. package/src/components/finance-charts/chart-host.tsx +236 -0
  269. package/src/components/finance-charts/chart-scale.ts +93 -0
  270. package/src/components/finance-charts/demo-data.ts +88 -0
  271. package/src/components/finance-charts/index.ts +46 -0
  272. package/src/components/finance-charts/lw-chart.tsx +292 -0
  273. package/src/components/finance-composites/MarketBar.tsx +243 -0
  274. package/src/components/finance-composites/OrderBook.tsx +133 -0
  275. package/src/components/finance-composites/PerformanceDashboard.tsx +185 -0
  276. package/src/components/finance-composites/SortableTable.tsx +144 -0
  277. package/src/components/finance-composites/StockTicker.tsx +65 -0
  278. package/src/components/finance-composites/SyncedTearsheet.tsx +226 -0
  279. package/src/components/finance-composites/index.ts +28 -0
  280. package/src/components/finance-tearsheet/KpiStrip.tsx +49 -0
  281. package/src/components/finance-tearsheet/LiveBadge.tsx +29 -0
  282. package/src/components/finance-tearsheet/ReturnsMatrix.tsx +321 -0
  283. package/src/components/finance-tearsheet/StrategyTearsheet.tsx +99 -0
  284. package/src/components/finance-tearsheet/TearsheetCard.tsx +54 -0
  285. package/src/components/finance-tearsheet/TradeLogTable.tsx +73 -0
  286. package/src/components/finance-tearsheet/charts.tsx +2624 -0
  287. package/src/components/finance-tearsheet/demo-data.ts +177 -0
  288. package/src/components/finance-tearsheet/format.ts +41 -0
  289. package/src/components/finance-tearsheet/index.ts +120 -0
  290. package/src/components/finance-tearsheet/print.ts +52 -0
  291. package/src/components/finance-tearsheet/risk-bands.ts +48 -0
  292. package/src/components/finance-tearsheet/stats.ts +35 -0
  293. package/src/components/finance-tearsheet/strategy-sheet-model.ts +238 -0
  294. package/src/components/finance-tearsheet/types.ts +58 -0
  295. package/src/components/footer/FooterCells.tsx +109 -0
  296. package/src/components/footer/index.ts +6 -0
  297. package/src/components/graph.tsx +158 -0
  298. package/src/components/icons.tsx +98 -0
  299. package/src/components/landing/PixelWordmark.tsx +36 -0
  300. package/src/components/logos.ts +39 -0
  301. package/src/components/marquee/Marquee.tsx +204 -0
  302. package/src/components/metrics/PerfMetrics.tsx +68 -0
  303. package/src/components/metrics/StatCounter.tsx +104 -0
  304. package/src/components/page-geometry.tsx +177 -0
  305. package/src/components/pricing/Pricing.tsx +174 -0
  306. package/src/components/pricing/PricingMatrix.tsx +219 -0
  307. package/src/components/prose/index.ts +2 -0
  308. package/src/components/prose/prose.tsx +86 -0
  309. package/src/components/repo-activity/RepoActivity.tsx +599 -0
  310. package/src/components/repo-activity/RepoHeatmap.tsx +225 -0
  311. package/src/components/repo-activity/demo.ts +201 -0
  312. package/src/components/repo-activity/fetch.ts +211 -0
  313. package/src/components/repo-activity/heatmap.ts +123 -0
  314. package/src/components/repo-activity/index.ts +28 -0
  315. package/src/components/repo-activity/types.ts +99 -0
  316. package/src/components/stages/NumberedStages.tsx +116 -0
  317. package/src/components/symbols/marks.tsx +116 -0
  318. package/src/components/symbols/terminal-lockup.tsx +136 -0
  319. package/src/components/symbols/terminal-marks.tsx +226 -0
  320. package/src/components/terminal/ChatResponseLoader.tsx +67 -0
  321. package/src/components/terminal/ContainerBootLoader.tsx +82 -0
  322. package/src/components/terminal/TerminalStepCaret.tsx +202 -0
  323. package/src/components/themeInit.ts +24 -0
  324. package/src/components/typography/MotionReveal.tsx +108 -0
  325. package/src/components/typography/TypewriterReveal.tsx +101 -0
  326. package/src/components/typography/WordReveal.tsx +143 -0
  327. package/src/data/modules.ts +378 -0
  328. package/src/data/subsystems.ts +101 -0
  329. package/src/index.ts +587 -0
  330. package/src/lib/base-path.ts +60 -0
  331. package/src/lib/cx.ts +25 -0
  332. package/src/lib/gloomberb.ts +69 -0
  333. package/src/lib/record.ts +19 -0
  334. package/src/lib/stub-payload.ts +183 -0
  335. package/src/lib/useBodyScrollLock.ts +44 -0
  336. package/src/lib/utils.ts +6 -0
  337. package/src/motion/primitives.tsx +105 -0
  338. package/src/motion/scroll-glide.ts +62 -0
  339. package/src/motion/scrolly-core.ts +38 -0
  340. package/src/motion/scrolly.tsx +95 -0
  341. package/src/navigation/HashScrollManager.tsx +167 -0
  342. package/src/navigation/hashScroll.ts +78 -0
  343. package/src/replay/browser.ts +280 -0
  344. package/src/replay/chat-export-run.ts +121 -0
  345. package/src/replay/export-format.ts +25 -0
  346. package/src/replay/export-mount.ts +32 -0
  347. package/src/replay/filename.ts +54 -0
  348. package/src/replay/index.ts +21 -0
  349. package/src/replay/redact-dom.ts +178 -0
  350. package/src/replay/redact.ts +113 -0
  351. package/src/replay/snapshot.ts +659 -0
  352. package/src/styles/account-auth.css +339 -0
  353. package/src/styles/brand-wordmark.css +11 -0
  354. package/src/styles/card-rail.css +120 -0
  355. package/src/styles/chat-aui.css +1143 -0
  356. package/src/styles/chat-core.css +469 -0
  357. package/src/styles/chat-digichat.css +2531 -0
  358. package/src/styles/chat-math.css +70 -0
  359. package/src/styles/chat-skin-palette.css +138 -0
  360. package/src/styles/chat-stock-menu.css +37 -0
  361. package/src/styles/chat-widgets.css +190 -0
  362. package/src/styles/command-palette.css +372 -0
  363. package/src/styles/controls-core.css +2261 -0
  364. package/src/styles/controls-overlay.css +603 -0
  365. package/src/styles/data-layout.css +288 -0
  366. package/src/styles/deck.css +141 -0
  367. package/src/styles/diagrams.css +1860 -0
  368. package/src/styles/digichat-app-theme.css +250 -0
  369. package/src/styles/digichat-boot-loader.css +487 -0
  370. package/src/styles/digichat-launcher.css +377 -0
  371. package/src/styles/docs.css +165 -0
  372. package/src/styles/effects-chrome.css +888 -0
  373. package/src/styles/finance-composites.css +214 -0
  374. package/src/styles/finance-tearsheet-overlay.css +90 -0
  375. package/src/styles/finance-tearsheet.css +945 -0
  376. package/src/styles/marquee.css +168 -0
  377. package/src/styles/metrics.css +82 -0
  378. package/src/styles/nav-shell.css +551 -0
  379. package/src/styles/pricing.css +79 -0
  380. package/src/styles/repo-activity.css +575 -0
  381. package/src/styles/stages.css +69 -0
  382. package/src/styles/terminal-loaders.css +122 -0
  383. package/src/styles/terminal-manifest.css +228 -0
  384. package/src/styles/web-theme.css +1007 -0
  385. package/src/styles/word-reveal.css +204 -0
  386. package/src/theme/apply-theme.ts +214 -0
  387. package/src/theme/index.ts +27 -0
  388. package/src/theme/palette-registry.ts +855 -0
  389. package/src/theme/tui-prefs.ts +64 -0
  390. package/src/ui/alert.tsx +78 -0
  391. package/src/ui/avatar.tsx +104 -0
  392. package/src/ui/badge.tsx +75 -0
  393. package/src/ui/breadcrumb.tsx +61 -0
  394. package/src/ui/button.tsx +67 -0
  395. package/src/ui/card.tsx +139 -0
  396. package/src/ui/checkbox.tsx +27 -0
  397. package/src/ui/collapsible.tsx +32 -0
  398. package/src/ui/date-pager.tsx +325 -0
  399. package/src/ui/dialog.tsx +157 -0
  400. package/src/ui/dropdown-menu.tsx +267 -0
  401. package/src/ui/empty-state.tsx +165 -0
  402. package/src/ui/field.tsx +104 -0
  403. package/src/ui/form.tsx +36 -0
  404. package/src/ui/icon-button.tsx +36 -0
  405. package/src/ui/index.ts +34 -0
  406. package/src/ui/input.tsx +33 -0
  407. package/src/ui/kbd.tsx +30 -0
  408. package/src/ui/label.tsx +32 -0
  409. package/src/ui/pager.tsx +119 -0
  410. package/src/ui/pagination.tsx +126 -0
  411. package/src/ui/radio-group.tsx +51 -0
  412. package/src/ui/search-bar.tsx +85 -0
  413. package/src/ui/segmented-control.tsx +76 -0
  414. package/src/ui/select.tsx +257 -0
  415. package/src/ui/separator.tsx +24 -0
  416. package/src/ui/sheet.tsx +141 -0
  417. package/src/ui/skeleton.tsx +76 -0
  418. package/src/ui/slider.tsx +76 -0
  419. package/src/ui/switch.tsx +31 -0
  420. package/src/ui/table.tsx +179 -0
  421. package/src/ui/tabs.tsx +89 -0
  422. package/src/ui/tags-input.tsx +168 -0
  423. package/src/ui/textarea.tsx +17 -0
  424. package/src/ui/tooltip.tsx +71 -0
@@ -0,0 +1,264 @@
1
+ "use client";
2
+ /** Shared nav, footer, and module card. Brand + links are passed in so both
3
+ * marketing apps reuse the same chrome. */
4
+ import { useRef, type AnchorHTMLAttributes, type ReactNode } from "react";
5
+ import { m, useScroll, useTransform } from "motion/react";
6
+ import type { VariantProps } from "class-variance-authority";
7
+ import { Emblem } from "./emblems";
8
+ import { StackRow } from "./StackLogo";
9
+ import { type ModuleNode } from "../data/modules";
10
+ import { useMotionSafe } from "../motion/primitives";
11
+ import { buttonVariants } from "../ui/button";
12
+ import { cn } from "../lib/utils";
13
+
14
+ export interface NavLink { label: string; href: string; external?: boolean; cta?: boolean; }
15
+
16
+ /** A labelled set of destinations — a dropdown menu on <NavShell/>'s wide bar,
17
+ * a labelled section of links in its narrow sheet. A group is a *sibling* type
18
+ * rather than an optional `items?` on NavLink because a group trigger has no
19
+ * destination of its own: making `href` optional to model that would widen it
20
+ * to `string | undefined` for every existing consumer and reader (the
21
+ * `key={l.href + l.label}` in this file included). A separate interface keeps
22
+ * `href` required where it belongs and makes "link or group" a real
23
+ * discriminated choice. */
24
+ export interface NavGroup { label: string; items: NavLink[]; }
25
+
26
+ /** One top-bar entry. `NavLink[]` stays assignable to `NavItem[]`, so every
27
+ * consumer that passes a flat link array today keeps compiling untouched. */
28
+ export type NavItem = NavLink | NavGroup;
29
+
30
+ /** Discriminates the NavItem union on the one field only a group carries. */
31
+ export function isNavGroup(item: NavItem): item is NavGroup {
32
+ return "items" in item;
33
+ }
34
+
35
+ /** The kit's vocabulary for a CTA's dress, taken straight from buttonVariants
36
+ * so there is exactly one button treatment in the canon — a CTA link is the
37
+ * same dress as the Button it pairs with, never a parallel class family. */
38
+ type ButtonVariant = NonNullable<VariantProps<typeof buttonVariants>["variant"]>;
39
+ type ButtonSize = NonNullable<VariantProps<typeof buttonVariants>["size"]>;
40
+
41
+ /** Absolute or protocol-relative href — never a same-page route. */
42
+ const isExternalHref = (href: string) => /^[a-z][a-z0-9+.-]*:/i.test(href) || href.startsWith("//");
43
+
44
+ /** Whether `href` names the page the visitor is currently on. Owned here so
45
+ * every site marks `aria-current` by the same rule: an exact pathname match
46
+ * only — never an external link, and never a same-page anchor (`/#pipeline`,
47
+ * `#section`) since several of those share one pathname and would all light
48
+ * up. Trailing slashes are equivalent (`/docs` ≡ `/docs/`, the static-export
49
+ * shape). */
50
+ export function hrefIsCurrent(href: string, currentPath?: string): boolean {
51
+ if (!currentPath || isExternalHref(href) || href.includes("#")) return false;
52
+ const path = href.split("?")[0] ?? href;
53
+ const norm = (p: string): string => {
54
+ if (p.length <= 1) return p;
55
+ let end = p.length;
56
+ while (end > 0 && p.charCodeAt(end - 1) === 47) end -= 1;
57
+ return p.slice(0, end);
58
+ };
59
+ return norm(path) === norm(currentPath);
60
+ }
61
+
62
+ /** Skip-to-content — visually hidden until keyboard focus, then pinned to the
63
+ * top-left. Token utilities (the pre-canon app copy is what this replaces);
64
+ * the consumer owns the target's `id` and focusability. */
65
+ export function SkipLink({
66
+ href = "#main",
67
+ children = "Skip to content",
68
+ className,
69
+ }: {
70
+ href?: string;
71
+ children?: ReactNode;
72
+ className?: string;
73
+ }) {
74
+ return (
75
+ <a
76
+ href={href}
77
+ className={cn(
78
+ "fixed top-2 left-2 z-[300] -translate-y-[200%] rounded-none bg-[var(--accent)] px-[0.9rem] py-2 font-mono text-[0.72rem] text-[var(--on-accent)] no-underline focus-visible:translate-y-0",
79
+ className,
80
+ )}
81
+ >
82
+ {children}
83
+ </a>
84
+ );
85
+ }
86
+
87
+ export interface CtaLinkProps extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>, "href"> {
88
+ href: string;
89
+ /** buttonVariants variant — the single CTA dress vocabulary. */
90
+ variant?: ButtonVariant;
91
+ size?: ButtonSize;
92
+ /** External destination: opens a new tab with the matching rel. */
93
+ external?: boolean;
94
+ /** Optional leading glyph, rendered before the label. */
95
+ icon?: ReactNode;
96
+ }
97
+
98
+ /** A CTA link dressed with the kit's button treatment. The kit shipped a
99
+ * `<Button>` (a real button) but no link equivalent, so every site hand-rolled
100
+ * `buttonVariants(...)` at the call site (or invented a `.dc-nav-cta` /
101
+ * `.dq-cta` class). This is that one treatment, named. */
102
+ export function CtaLink({
103
+ href,
104
+ children,
105
+ variant = "default",
106
+ size = "sm",
107
+ external,
108
+ icon,
109
+ className,
110
+ ...rest
111
+ }: CtaLinkProps) {
112
+ return (
113
+ <a
114
+ href={href}
115
+ className={cn(buttonVariants({ variant, size }), className)}
116
+ target={external ? "_blank" : undefined}
117
+ rel={external ? "noopener noreferrer" : undefined}
118
+ {...rest}
119
+ >
120
+ {icon}
121
+ {children}
122
+ </a>
123
+ );
124
+ }
125
+
126
+ export interface IconLinkProps
127
+ extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>, "href" | "aria-label"> {
128
+ href: string;
129
+ /** The accessible name — required, because the child is a bare glyph. */
130
+ label: string;
131
+ external?: boolean;
132
+ }
133
+
134
+ /** Icon-only link (the nav tail's GitHub glyph, a mark-only CTA). The kit's
135
+ * ghost icon-button dress, with the `aria-label` the bare svg needs made
136
+ * mandatory rather than left to each call site. */
137
+ export function IconLink({ href, label, external, className, children, ...rest }: IconLinkProps) {
138
+ return (
139
+ <a
140
+ href={href}
141
+ aria-label={label}
142
+ className={cn(buttonVariants({ variant: "ghost", size: "icon-sm" }), className)}
143
+ target={external ? "_blank" : undefined}
144
+ rel={external ? "noopener noreferrer" : undefined}
145
+ {...rest}
146
+ >
147
+ {children}
148
+ </a>
149
+ );
150
+ }
151
+
152
+ export function Footer({
153
+ links,
154
+ meta,
155
+ profiles,
156
+ }: {
157
+ links: NavLink[];
158
+ meta: string;
159
+ /** Quiet company-profile row (typically <SocialRow/>). Optional so existing
160
+ * call sites stay a utility-link strip; socials are a dedicated primitive,
161
+ * not a fake Connect column. */
162
+ profiles?: ReactNode;
163
+ }) {
164
+ return (
165
+ <footer className="footer">
166
+ <div className="wrap footer-inner">
167
+ <nav className="footer-links" aria-label="Footer">
168
+ {links.map((l) => (
169
+ <a key={l.href + l.label} href={l.href} target={l.external ? "_blank" : undefined}
170
+ rel={l.external ? "noopener noreferrer" : undefined}>{l.label}</a>
171
+ ))}
172
+ </nav>
173
+ {profiles}
174
+ <p className="footer-meta">{meta}</p>
175
+ </div>
176
+ </footer>
177
+ );
178
+ }
179
+
180
+ /** The page's last word (canon §08): the module's name at giant scale,
181
+ * 1px hairline outline by default — plus an opt-in scroll-scrubbed glow
182
+ * sweep. The outline rise/fill is scroll-scrubbed with zero JS via CSS
183
+ * `animation-timeline: view()` (@supports-gated in site.css); under
184
+ * reduced motion or without support the name simply stands. The suffix
185
+ * wears var(--accent), so each app's livery (or the umbrella's ink)
186
+ * dresses it automatically. aria-hidden: punctuation, not content.
187
+ *
188
+ * `sweep` (default false — the outline-only ruling stands for every
189
+ * existing consumer) adds the reference footer's personality moment: an
190
+ * accent glow passing left→right across the wordmark, scrubbed by the
191
+ * colophon's own scroll progress (same offsets/transform as
192
+ * reference/components/footer-reference.tsx). A duplicate overlay span
193
+ * carries the gradient clipped to its glyphs; reduced motion (and no-JS)
194
+ * park the band off-screen so no glow travels. Requires a MotionProvider
195
+ * in the consuming app.
196
+ *
197
+ * The rise/fill only works because `.colophon` clips with `overflow-x: clip`
198
+ * rather than `overflow: hidden` (site.css): `hidden` made the colophon its
199
+ * own child's nearest scrollport, so `.colo-word`'s `view()` timeline
200
+ * resolved against a box that never scrolls, saturated, and the whole
201
+ * scroll-scrub silently no-opped (the mark sat fully risen at every scroll
202
+ * position). Do not "simplify" that back to `overflow: hidden`. */
203
+ export function Colophon({
204
+ name,
205
+ suffix,
206
+ sweep = false,
207
+ }: {
208
+ name: string;
209
+ suffix?: string;
210
+ sweep?: boolean;
211
+ }) {
212
+ const ref = useRef<HTMLDivElement | null>(null);
213
+ // useMotionSafe(), not raw useReducedMotion() -- see WordReveal.tsx's fix
214
+ // comment (#2244) for the full hydration-mismatch mechanism this avoids.
215
+ const reduced = !useMotionSafe();
216
+ // 0 = the colophon's top enters the viewport bottom; 1 = scrolled to its
217
+ // end. The band travels off-left → off-right across the middle of that
218
+ // range, so the highlight crosses the wordmark once as you scroll into it.
219
+ const { scrollYProgress } = useScroll({
220
+ target: ref,
221
+ offset: ["start end", "end end"],
222
+ });
223
+ const sweepPos = useTransform(scrollYProgress, [0.38, 0.96], [120, -20]);
224
+ const backgroundPosition = useTransform(sweepPos, (v) => `${v}% 0`);
225
+
226
+ return (
227
+ <div className="colophon" aria-hidden="true" ref={ref}>
228
+ <span className="colo-word">
229
+ {name}
230
+ {suffix ? <b>{suffix}</b> : null}
231
+ {sweep ? (
232
+ <m.span
233
+ className="colo-sweep"
234
+ style={reduced ? undefined : { backgroundPosition }}
235
+ >
236
+ {name}
237
+ {suffix}
238
+ </m.span>
239
+ ) : null}
240
+ </span>
241
+ </div>
242
+ );
243
+ }
244
+
245
+ export function ModuleCard({
246
+ m,
247
+ hrefForModule = (id: string) => `/modules/${id}`,
248
+ }: {
249
+ m: ModuleNode;
250
+ /** Host-owned module URL. Defaults to the `/modules/[id]` host contract. */
251
+ hrefForModule?: (id: string) => string;
252
+ }) {
253
+ return (
254
+ <a className={`mod-card t-${m.tier}`} href={hrefForModule(m.id)}>
255
+ <div className="mod-card-top">
256
+ <Emblem id={m.emblem} size={26} />
257
+ <span className={`dg-tier t-${m.tier}`}>{m.tier}</span>
258
+ </div>
259
+ <h3>{m.name}</h3>
260
+ <p className="role">{m.role}</p>
261
+ <StackRow items={m.stack.slice(0, 4)} className="stack-row compact" />
262
+ </a>
263
+ );
264
+ }
@@ -0,0 +1,404 @@
1
+ "use client";
2
+
3
+ import {
4
+ useEffect,
5
+ useId,
6
+ useLayoutEffect,
7
+ useRef,
8
+ useState,
9
+ type ReactNode,
10
+ } from "react";
11
+ import { createPortal } from "react-dom";
12
+ import { useBodyScrollLock } from "../../lib/useBodyScrollLock";
13
+
14
+ /**
15
+ * CommandPalette — the ⌘K command bar shell promoted from the design
16
+ * reference (chrome/command-palette). The shell owns the chrome and the
17
+ * interaction contract: overlay/backdrop, a document.body portal, the search
18
+ * input row, the grouped listbox, the keyboard loop (↑/↓/Home/End/↵/esc,
19
+ * focus held on the input) and the listbox ARIA wiring — `role="listbox"`
20
+ * with `role="option"` children and `aria-activedescendant` on the listbox
21
+ * (the pattern the dashboard's palette was fixed to; preserved here).
22
+ *
23
+ * The CONSUMER owns everything data- and router-shaped: the `open` flag (and
24
+ * whatever global shortcut toggles it — the shell binds no ⌘K), the grouped
25
+ * item list — recomputed per keystroke when `groups` is a function of the
26
+ * query — and per-item `onSelect` callbacks (router.push, theme toggles, …).
27
+ * No router, data fetching, or filtering policy lives here.
28
+ *
29
+ * Query state is internal and resets on every open, synchronously on the
30
+ * opening render, so a reopen never flashes the previous query. Enter or
31
+ * click selects the active item: palette-level `onSelect(item)` fires, then
32
+ * `item.onSelect()`, then `onClose()` unless `closeOnSelect={false}`.
33
+ *
34
+ * Wiring (in the consuming app's globals.css):
35
+ * @import "@digithings-ai/ui/styles/command-palette.css";
36
+ * @source "<path-to>/packages/ui/src/components/command-palette";
37
+ * Overlay stacking: the overlay's z-index reads var(--cp-z, 40) — set --cp-z
38
+ * at the call site when the app's chrome stacks higher than the reference's.
39
+ *
40
+ * Two dresses (styles/command-palette.css):
41
+ * - dress="reference" (default): the design-reference dress — mono rows,
42
+ * surface-mix panel, token scrim.
43
+ * - dress="glass": the dashboard's shipped ⌘K look, translated
44
+ * exactly — black/75 blurred scrim, term-bg panel (32rem, 12px radius),
45
+ * sans rows with accent-ring active state.
46
+ */
47
+ export type CommandPaletteItem = {
48
+ /** Stable identity — React key; option DOM ids are index-derived. */
49
+ id: string;
50
+ label: ReactNode;
51
+ /** Optional muted second line under the label (the dashboard "hint"). */
52
+ description?: ReactNode;
53
+ /** Fired when the row is chosen (Enter or click) — router.push lives here. */
54
+ onSelect?: () => void;
55
+ /** Optional right-edge keycap hint — "B", "⌘K". */
56
+ kbd?: string;
57
+ /** Livery scope class for the row (`accent-<livery>`) — colors the dot. */
58
+ livery?: string;
59
+ /** Show the leading livery dot (reference grammar). */
60
+ dot?: boolean;
61
+ /** Leading icon node — wins over `dot` when both are set. */
62
+ icon?: ReactNode;
63
+ };
64
+
65
+ export type CommandPaletteGroup = {
66
+ id?: string;
67
+ /** Optional group heading; unlabeled groups render without `role="group"`. */
68
+ label?: ReactNode;
69
+ items: CommandPaletteItem[];
70
+ };
71
+
72
+ export type CommandPaletteProps = {
73
+ /** Controlled visibility — the consumer owns the flag and its shortcut. */
74
+ open: boolean;
75
+ /** Fired by esc, the scrim, and (by default) after a selection. */
76
+ onClose: () => void;
77
+ /**
78
+ * The grouped item list. Pass a function to recompute it per keystroke —
79
+ * it receives the shell's current query verbatim (filtering policy is
80
+ * entirely the consumer's).
81
+ */
82
+ groups: CommandPaletteGroup[] | ((query: string) => CommandPaletteGroup[]);
83
+ /** Palette-level selection hook, fired before the item's own onSelect. */
84
+ onSelect?: (item: CommandPaletteItem) => void;
85
+ /** Close the palette after a selection (default true). */
86
+ closeOnSelect?: boolean;
87
+ placeholder?: string;
88
+ /**
89
+ * Footer key-hint legend: `true` renders the standard ↑↓ / ↵ / esc legend,
90
+ * a node renders custom content inside `.cp-foot`, absent renders nothing.
91
+ */
92
+ footer?: ReactNode | boolean;
93
+ /** Trailing input-row slot — defaults to an `esc` keycap. */
94
+ inputTrailing?: ReactNode;
95
+ /** Leading input-row slot — defaults to the built-in magnifier glyph. */
96
+ inputLeading?: ReactNode;
97
+ /**
98
+ * Input `type` attribute (default "text"). dashboard ships type="search"
99
+ * (native search-cancel affordance in WebKit/Chromium).
100
+ */
101
+ inputType?: string;
102
+ /** Empty-results content; the function form receives the current query. */
103
+ emptyMessage?: ReactNode | ((query: string) => ReactNode);
104
+ /** Dialog aria-label (default "Command palette"). */
105
+ ariaLabel?: string;
106
+ /** Listbox aria-label (default "Commands"). */
107
+ listLabel?: string;
108
+ /** Input aria-label (default "Search commands"). */
109
+ inputAriaLabel?: string;
110
+ /** Extra class on the panel. */
111
+ className?: string;
112
+ /** Render into a document.body portal (default) or in place. */
113
+ portal?: boolean;
114
+ /** Visual dress — "reference" (default) or dashboard's "glass". */
115
+ dress?: "reference" | "glass";
116
+ };
117
+
118
+ const defaultEmpty = (query: string): ReactNode =>
119
+ query ? <>No matches for “{query}”.</> : "No matches.";
120
+
121
+ // Focus-trap boundary query — deliberately generic, not "just the input":
122
+ // consumers can pass interactive inputLeading/inputTrailing content (dashboard
123
+ // ships a real Close button there), so the trap must cycle the dialog's
124
+ // actual focusable set rather than assume the input is the only one.
125
+ const FOCUSABLE_SELECTOR =
126
+ 'a[href]:not([tabindex^="-"]), button:not([disabled]):not([tabindex^="-"]), input:not([disabled]):not([tabindex^="-"]), select:not([disabled]):not([tabindex^="-"]), textarea:not([disabled]):not([tabindex^="-"]), [tabindex]:not([tabindex^="-"])';
127
+
128
+ export function CommandPalette({
129
+ open,
130
+ onClose,
131
+ groups,
132
+ onSelect,
133
+ closeOnSelect = true,
134
+ placeholder = "Type a command or search…",
135
+ footer,
136
+ inputTrailing,
137
+ inputLeading,
138
+ inputType = "text",
139
+ emptyMessage,
140
+ ariaLabel = "Command palette",
141
+ listLabel = "Commands",
142
+ inputAriaLabel = "Search commands",
143
+ className,
144
+ portal = true,
145
+ dress = "reference",
146
+ }: CommandPaletteProps) {
147
+ const baseId = useId();
148
+ const [query, setQuery] = useState("");
149
+ const [active, setActive] = useState(0);
150
+ const [mounted, setMounted] = useState(false);
151
+ const listRef = useRef<HTMLDivElement | null>(null);
152
+ const panelRef = useRef<HTMLDivElement | null>(null);
153
+
154
+ // Reset synchronously on the opening render — a reopen never flashes the
155
+ // previous query (React's adjust-state-during-render pattern).
156
+ const [prevOpen, setPrevOpen] = useState(open);
157
+ if (open !== prevOpen) {
158
+ setPrevOpen(open);
159
+ if (open) {
160
+ setQuery("");
161
+ setActive(0);
162
+ }
163
+ }
164
+
165
+ useEffect(() => setMounted(true), []);
166
+
167
+ // Lock body scroll while the dialog is open — reference-counted (see
168
+ // useBodyScrollLock) so this coordinates safely with any other overlay
169
+ // (e.g. NavShell's own menu) that may be locked at the same time,
170
+ // regardless of open/close order.
171
+ useBodyScrollLock(open);
172
+
173
+ const resolvedGroups = typeof groups === "function" ? groups(query) : groups;
174
+ const flat = resolvedGroups.flatMap((g) => g.items);
175
+ const count = flat.length;
176
+ const activeIndex = count === 0 ? -1 : Math.min(active, count - 1);
177
+
178
+ const select = (item: CommandPaletteItem) => {
179
+ onSelect?.(item);
180
+ item.onSelect?.();
181
+ if (closeOnSelect) onClose();
182
+ };
183
+
184
+ // Refs keep one keydown listener per open, not one per keystroke.
185
+ const flatRef = useRef(flat);
186
+ const activeRef = useRef(activeIndex);
187
+ const selectRef = useRef(select);
188
+ const onCloseRef = useRef(onClose);
189
+ useLayoutEffect(() => {
190
+ flatRef.current = flat;
191
+ activeRef.current = activeIndex;
192
+ selectRef.current = select;
193
+ onCloseRef.current = onClose;
194
+ });
195
+
196
+ useEffect(() => {
197
+ if (!open) return;
198
+ const onKey = (e: KeyboardEvent) => {
199
+ if (e.key === "Escape") {
200
+ e.preventDefault();
201
+ onCloseRef.current();
202
+ return;
203
+ }
204
+ if (e.key === "Tab") {
205
+ // Focus trap: only intercept at the dialog's boundary — wrap
206
+ // Tab past the last focusable back to the first, and Shift+Tab
207
+ // before the first back to the last — rather than pinning focus to
208
+ // the input unconditionally, since a consumer's inputLeading/
209
+ // inputTrailing slot can carry its own focusable content (dashboard's
210
+ // production palette ships a real Close button there).
211
+ const panel = panelRef.current;
212
+ if (!panel) return;
213
+ const focusables = Array.from(panel.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR));
214
+ if (focusables.length === 0) return;
215
+ const first = focusables[0];
216
+ const last = focusables[focusables.length - 1];
217
+ const activeEl = document.activeElement;
218
+ if (e.shiftKey) {
219
+ if (activeEl === first || !panel.contains(activeEl)) {
220
+ e.preventDefault();
221
+ last.focus();
222
+ }
223
+ } else {
224
+ if (activeEl === last || !panel.contains(activeEl)) {
225
+ e.preventDefault();
226
+ first.focus();
227
+ }
228
+ }
229
+ return;
230
+ }
231
+ const len = flatRef.current.length;
232
+ if (len === 0) return;
233
+ if (e.key === "ArrowDown") {
234
+ e.preventDefault();
235
+ setActive((i) => Math.min(i + 1, len - 1));
236
+ } else if (e.key === "ArrowUp") {
237
+ e.preventDefault();
238
+ setActive((i) => Math.max(i - 1, 0));
239
+ } else if (e.key === "Home") {
240
+ e.preventDefault();
241
+ setActive(0);
242
+ } else if (e.key === "End") {
243
+ e.preventDefault();
244
+ setActive(len - 1);
245
+ } else if (e.key === "Enter") {
246
+ e.preventDefault();
247
+ const idx = Math.min(Math.max(0, activeRef.current), len - 1);
248
+ const item = flatRef.current[idx];
249
+ if (item) selectRef.current(item);
250
+ }
251
+ };
252
+ window.addEventListener("keydown", onKey);
253
+ return () => window.removeEventListener("keydown", onKey);
254
+ }, [open]);
255
+
256
+ // Keep the active row in view as the keyboard walks the list.
257
+ useLayoutEffect(() => {
258
+ if (!open || activeIndex < 0 || !listRef.current) return;
259
+ listRef.current
260
+ .querySelector(`[data-cp-index="${activeIndex}"]`)
261
+ ?.scrollIntoView({ block: "nearest" });
262
+ }, [open, activeIndex, query]);
263
+
264
+ if (!open) return null;
265
+
266
+ const empty =
267
+ typeof emptyMessage === "function"
268
+ ? emptyMessage(query)
269
+ : emptyMessage ?? defaultEmpty(query);
270
+
271
+ let flatIndex = -1;
272
+ const palette = (
273
+ <div
274
+ className={`cp-overlay${dress === "glass" ? " cp-overlay--glass" : ""}`}
275
+ role="dialog"
276
+ aria-modal="true"
277
+ aria-label={ariaLabel}
278
+ >
279
+ <div className="cp-scrim" onClick={onClose} aria-hidden="true" />
280
+ <div ref={panelRef} className={`cp-panel${className ? ` ${className}` : ""}`}>
281
+ <div className="cp-input-row">
282
+ {inputLeading !== undefined ? (
283
+ inputLeading
284
+ ) : (
285
+ <span className="cp-search-glyph" aria-hidden="true">
286
+ <svg
287
+ viewBox="0 0 24 24"
288
+ width="16"
289
+ height="16"
290
+ fill="none"
291
+ stroke="currentColor"
292
+ strokeWidth="1.6"
293
+ >
294
+ <circle cx="11" cy="11" r="7" />
295
+ <path d="M20 20l-3.5-3.5" strokeLinecap="round" />
296
+ </svg>
297
+ </span>
298
+ )}
299
+ <input
300
+ className="cp-input"
301
+ type={inputType}
302
+ placeholder={placeholder}
303
+ value={query}
304
+ autoFocus
305
+ autoComplete="off"
306
+ spellCheck={false}
307
+ aria-label={inputAriaLabel}
308
+ onChange={(e) => {
309
+ setQuery(e.target.value);
310
+ setActive(0);
311
+ }}
312
+ />
313
+ {inputTrailing !== undefined ? inputTrailing : <kbd className="cp-kbd">esc</kbd>}
314
+ </div>
315
+
316
+ <div
317
+ ref={listRef}
318
+ className="cp-results"
319
+ role="listbox"
320
+ aria-label={listLabel}
321
+ aria-activedescendant={activeIndex >= 0 ? `${baseId}-opt-${activeIndex}` : undefined}
322
+ >
323
+ {count === 0 ? (
324
+ <p className="cp-empty" role="presentation">
325
+ {empty}
326
+ </p>
327
+ ) : (
328
+ resolvedGroups.map((g, gi) => {
329
+ if (g.items.length === 0) return null;
330
+ const labelId = g.label != null ? `${baseId}-grp-${gi}` : undefined;
331
+ const rows = g.items.map((item) => {
332
+ flatIndex += 1;
333
+ const idx = flatIndex;
334
+ const on = idx === activeIndex;
335
+ return (
336
+ <div
337
+ key={item.id}
338
+ id={`${baseId}-opt-${idx}`}
339
+ role="option"
340
+ aria-selected={on}
341
+ data-cp-index={idx}
342
+ className={`cp-row${on ? " on" : ""}${item.livery ? ` accent-${item.livery}` : ""}`}
343
+ onClick={() => select(item)}
344
+ onMouseEnter={() => setActive(idx)}
345
+ >
346
+ {item.icon ? (
347
+ <span className="cp-icon" aria-hidden="true">
348
+ {item.icon}
349
+ </span>
350
+ ) : item.dot ? (
351
+ <span className="cp-dot" aria-hidden="true" />
352
+ ) : null}
353
+ <span className="cp-label">
354
+ {item.label}
355
+ {item.description != null ? (
356
+ <span className="cp-desc">{item.description}</span>
357
+ ) : null}
358
+ </span>
359
+ {item.kbd ? <kbd className="cp-kbd cp-row-key">{item.kbd}</kbd> : null}
360
+ </div>
361
+ );
362
+ });
363
+ return g.label != null ? (
364
+ <div key={g.id ?? gi} className="cp-group" role="group" aria-labelledby={labelId}>
365
+ <p className="cp-group-label" id={labelId} role="presentation">
366
+ {g.label}
367
+ </p>
368
+ {rows}
369
+ </div>
370
+ ) : (
371
+ <div key={g.id ?? gi} className="cp-group" role="presentation">
372
+ {rows}
373
+ </div>
374
+ );
375
+ })
376
+ )}
377
+ </div>
378
+
379
+ {footer === true ? (
380
+ <div className="cp-foot" aria-hidden="true">
381
+ <span>
382
+ <kbd className="cp-kbd">↑</kbd>
383
+ <kbd className="cp-kbd">↓</kbd> navigate
384
+ </span>
385
+ <span>
386
+ <kbd className="cp-kbd">↵</kbd> open
387
+ </span>
388
+ <span>
389
+ <kbd className="cp-kbd">esc</kbd> close
390
+ </span>
391
+ </div>
392
+ ) : footer ? (
393
+ <div className="cp-foot" aria-hidden="true">
394
+ {footer}
395
+ </div>
396
+ ) : null}
397
+ </div>
398
+ </div>
399
+ );
400
+
401
+ if (!portal) return palette;
402
+ if (!mounted) return null;
403
+ return createPortal(palette, document.body);
404
+ }
@@ -0,0 +1,6 @@
1
+ export {
2
+ CommandPalette,
3
+ type CommandPaletteProps,
4
+ type CommandPaletteGroup,
5
+ type CommandPaletteItem,
6
+ } from "./CommandPalette";