@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,115 @@
1
+ /**
2
+ * The one place mermaid gets told what colour anything is.
3
+ *
4
+ * Extracted from `ChatMermaidBlock` so every mermaid surface in the kit — the
5
+ * chat fence and the architecture diagram — resolves the same palette the same
6
+ * way, and a token added for one is a token added for both.
7
+ *
8
+ * THE RULE: mermaid gets `theme: "base"` — the only built-in palette that lets
9
+ * every `themeVariable` through — and every variable is filled from the
10
+ * *computed* value of a design token read off the host element. Reading off the
11
+ * host rather than `:root` means a livery scope (`.accent-digichat`,
12
+ * `.accent-digiquant`) or a nested `[data-theme]` resolves exactly the colours
13
+ * the surrounding section resolves.
14
+ *
15
+ * Tokens that do not resolve (tokens.css not loaded, non-browser) are omitted,
16
+ * so mermaid falls back to its own value for that variable rather than being
17
+ * handed an empty string it cannot parse.
18
+ */
19
+
20
+ /**
21
+ * mermaid themeVariable -> design token. Kept explicit (rather than letting
22
+ * mermaid derive everything from `primaryColor`) because its derivation runs
23
+ * khroma lighten/darken on whatever it is given, and the canon palette is not a
24
+ * ramp — `--ink-mute` is the line colour in both themes, not a shade of the
25
+ * node fill. Diagram kinds beyond flowchart borrow these too: the sequence,
26
+ * state, class, ER and architecture renderers all read actor/label/note/
27
+ * attribute/border vars.
28
+ */
29
+ export const THEME_TOKENS: ReadonlyArray<readonly [string, string]> = [
30
+ // canvas + type
31
+ ["background", "--surface"],
32
+ ["textColor", "--ink-soft"],
33
+ ["titleColor", "--ink"],
34
+ // nodes
35
+ ["primaryColor", "--surface-2"],
36
+ ["primaryTextColor", "--ink"],
37
+ ["primaryBorderColor", "--accent"],
38
+ ["secondaryColor", "--surface"],
39
+ ["secondaryTextColor", "--ink"],
40
+ ["secondaryBorderColor", "--hair-2"],
41
+ ["tertiaryColor", "--surface"],
42
+ ["tertiaryTextColor", "--ink-soft"],
43
+ ["tertiaryBorderColor", "--hair-2"],
44
+ ["mainBkg", "--surface-2"],
45
+ ["nodeBorder", "--accent"],
46
+ ["nodeTextColor", "--ink"],
47
+ // edges + clusters
48
+ ["lineColor", "--ink-mute"],
49
+ ["edgeLabelBackground", "--surface"],
50
+ ["clusterBkg", "--surface"],
51
+ ["clusterBorder", "--hair-2"],
52
+ // architecture (v11 architecture-beta)
53
+ ["archEdgeColor", "--ink-mute"],
54
+ ["archEdgeArrowColor", "--ink-mute"],
55
+ ["archGroupBorderColor", "--hair-2"],
56
+ // sequence
57
+ ["actorBkg", "--surface-2"],
58
+ ["actorBorder", "--accent"],
59
+ ["actorTextColor", "--ink"],
60
+ ["actorLineColor", "--ink-mute"],
61
+ ["signalColor", "--ink-mute"],
62
+ ["signalTextColor", "--ink-soft"],
63
+ ["activationBkgColor", "--surface-2"],
64
+ ["activationBorderColor", "--accent"],
65
+ ["sequenceNumberColor", "--surface"],
66
+ ["labelBoxBkgColor", "--surface-2"],
67
+ ["labelBoxBorderColor", "--hair-2"],
68
+ ["labelTextColor", "--ink"],
69
+ ["loopTextColor", "--ink-soft"],
70
+ ["noteBkgColor", "--surface"],
71
+ ["noteBorderColor", "--hair-2"],
72
+ ["noteTextColor", "--ink-soft"],
73
+ ["altBackground", "--surface-2"],
74
+ // class / ER / state
75
+ ["classText", "--ink"],
76
+ ["attributeBackgroundColorOdd", "--surface"],
77
+ ["attributeBackgroundColorEven", "--surface-2"],
78
+ // pie / quadrant chrome (categorical slice hues stay mermaid's — the canon
79
+ // has no categorical ramp in the token layer)
80
+ ["pieTitleTextColor", "--ink"],
81
+ ["pieSectionTextColor", "--ink"],
82
+ ["pieLegendTextColor", "--ink-soft"],
83
+ ["pieStrokeColor", "--surface"],
84
+ ["pieOuterStrokeColor", "--hair-2"],
85
+ // failure chrome — the negative-semantics token, so a bad node reads red in
86
+ // both themes without a literal
87
+ ["errorBkgColor", "--surface"],
88
+ ["errorTextColor", "--down"],
89
+ ];
90
+
91
+ /**
92
+ * Stroke widths are numbers, not colours, so they cannot come from a token —
93
+ * they are pinned here to the kit's hairline weight so an architecture diagram
94
+ * draws at the same weight as every other rule on the page.
95
+ */
96
+ const THEME_LITERALS: ReadonlyArray<readonly [string, string]> = [
97
+ ["archEdgeWidth", "1.4"],
98
+ ["archGroupBorderWidth", "1"],
99
+ ];
100
+
101
+ /** Resolve THEME_TOKENS against `host`'s computed style. */
102
+ export function tokenThemeVariables(host: Element): Record<string, string | boolean> {
103
+ const cs = window.getComputedStyle(host);
104
+ const vars: Record<string, string | boolean> = {};
105
+ for (const [key, token] of THEME_TOKENS) {
106
+ const value = cs.getPropertyValue(token).trim();
107
+ if (value) vars[key] = value;
108
+ }
109
+ for (const [key, value] of THEME_LITERALS) vars[key] = value;
110
+ const mono = cs.getPropertyValue("--font-mono").trim();
111
+ if (mono) vars.fontFamily = mono;
112
+ // mermaid's own lighten/darken direction for anything it still derives.
113
+ vars.darkMode = document.documentElement.getAttribute("data-theme") !== "light";
114
+ return vars;
115
+ }
@@ -0,0 +1,171 @@
1
+ /**
2
+ * Scroll pose for the architecture tour camera.
3
+ *
4
+ * The walk is one 0..1 progress value. Each step owns an equal slice. The
5
+ * camera rests on that step's boxes through the parked midpoint (where a
6
+ * single wheel gesture settles), then glides to the next step's boxes so it
7
+ * arrives as the narration crosses the boundary.
8
+ */
9
+
10
+ export interface CamFrame {
11
+ k: number;
12
+ x: number;
13
+ y: number;
14
+ }
15
+
16
+ export const CAM_IDENTITY: CamFrame = { k: 1, x: 0, y: 0 };
17
+
18
+ export interface Box {
19
+ x: number;
20
+ y: number;
21
+ w: number;
22
+ h: number;
23
+ }
24
+
25
+ export interface SpotRect {
26
+ left: number;
27
+ top: number;
28
+ width: number;
29
+ height: number;
30
+ }
31
+
32
+ const clamp01 = (n: number): number => Math.max(0, Math.min(1, n));
33
+
34
+ const smooth = (n: number): number => {
35
+ const t = clamp01(n);
36
+ return t * t * (3 - 2 * t);
37
+ };
38
+
39
+ /** Active step and the 0..1 position inside it. Progress 1 stays on the last step. */
40
+ export function walkCursor(progress: number, count: number): { index: number; frac: number } {
41
+ if (count <= 0) return { index: 0, frac: 0 };
42
+ const v = clamp01(progress) * count;
43
+ const index = Math.min(count - 1, Math.floor(v));
44
+ const frac = v - index;
45
+ return { index, frac: clamp01(frac) };
46
+ }
47
+
48
+ /**
49
+ * 0 while the reader is parked on this step's boxes, then 0→1 across the
50
+ * tail of the step so the camera is on the next boxes as the step index flips.
51
+ */
52
+ export function glideAmount(frac: number): number {
53
+ return smooth((frac - 0.55) / 0.45);
54
+ }
55
+
56
+ export function lerpCam(a: CamFrame, b: CamFrame, t: number): CamFrame {
57
+ const u = clamp01(t);
58
+ return {
59
+ k: a.k + (b.k - a.k) * u,
60
+ x: a.x + (b.x - a.x) * u,
61
+ y: a.y + (b.y - a.y) * u,
62
+ };
63
+ }
64
+
65
+ export function camTransform(frame: CamFrame): string {
66
+ if (frame.k < 1.02 && Math.abs(frame.x) < 0.5 && Math.abs(frame.y) < 0.5) return "";
67
+ return `scale(${frame.k.toFixed(4)}) translate(${frame.x.toFixed(2)}px, ${frame.y.toFixed(2)}px)`;
68
+ }
69
+
70
+ /**
71
+ * Frame the lit boxes. Zoom is capped so a small pair of boxes cannot crop
72
+ * the rest of the drawing out of the stage, and a step that already fills
73
+ * the frame (the whole-graph opener) stays at identity.
74
+ */
75
+ export function fitCamera(opts: {
76
+ boxes: Box[];
77
+ stageW: number;
78
+ stageH: number;
79
+ contentW: number;
80
+ contentH: number;
81
+ /** Fraction of the stage the target should occupy. */
82
+ fill?: number;
83
+ maxScale?: number;
84
+ /**
85
+ * Contain (default) fits the whole cluster in the stage, so a wide row
86
+ * never zooms. Cover zooms on the tighter axis, up to `maxScale`, so a
87
+ * wide short row still travels into view.
88
+ */
89
+ cover?: boolean;
90
+ /** Stage pixels kept clear around the lit boxes, so the glow is never cut. */
91
+ pad?: number;
92
+ }): CamFrame {
93
+ const { boxes, stageW, stageH, contentW, contentH } = opts;
94
+ const fill = opts.fill ?? 0.34;
95
+ const maxScale = opts.maxScale ?? 1.45;
96
+ const pad = opts.pad ?? 24;
97
+ if (boxes.length === 0 || stageW <= 0 || stageH <= 0 || contentW <= 0 || contentH <= 0) {
98
+ return CAM_IDENTITY;
99
+ }
100
+ const minX = Math.min(...boxes.map((b) => b.x));
101
+ const minY = Math.min(...boxes.map((b) => b.y));
102
+ const maxX = Math.max(...boxes.map((b) => b.x + b.w));
103
+ const maxY = Math.max(...boxes.map((b) => b.y + b.h));
104
+ const width = Math.max(maxX - minX, 1);
105
+ const height = Math.max(maxY - minY, 1);
106
+ /* A cluster that already covers the drawing is the whole-graph beat.
107
+ Cover would otherwise zoom the letterboxed margin around it. */
108
+ if (width / contentW > 0.7 && height / contentH > 0.62) return CAM_IDENTITY;
109
+ const contain = Math.min(
110
+ Math.max(stageW - 2 * pad, 1) / width,
111
+ Math.max(stageH - 2 * pad, 1) / height,
112
+ );
113
+ const cover = Math.max(stageW / width, stageH / height);
114
+ // Cover never crops the lit boxes: it stops at the padded contain fit.
115
+ const zoom = opts.cover ? Math.min(fill * cover, contain) : fill * contain;
116
+ const k = Math.max(1, Math.min(maxScale, zoom));
117
+ if (k < 1.02) return CAM_IDENTITY;
118
+ const cx = (minX + maxX) / 2;
119
+ const cy = (minY + maxY) / 2;
120
+ const pan = (span: number, content: number, centre: number): number => {
121
+ const low = span / k - content;
122
+ if (low > 0) return low / 2;
123
+ return Math.min(0, Math.max(low, span / (2 * k) - centre));
124
+ };
125
+ return { k, x: pan(stageW, contentW, cx), y: pan(stageH, contentH, cy) };
126
+ }
127
+
128
+ /** Spotlight over the lit boxes. A union that already covers the drawing is the
129
+ whole-graph beat — no spotlight, the full graph stays lit. */
130
+ export function unionSpot(
131
+ boxes: Box[],
132
+ contentW: number,
133
+ contentH: number,
134
+ pad = 6,
135
+ ): SpotRect | null {
136
+ if (boxes.length === 0 || contentW <= 0 || contentH <= 0) return null;
137
+ const minX = Math.min(...boxes.map((b) => b.x));
138
+ const minY = Math.min(...boxes.map((b) => b.y));
139
+ const maxX = Math.max(...boxes.map((b) => b.x + b.w));
140
+ const maxY = Math.max(...boxes.map((b) => b.y + b.h));
141
+ const width = Math.max(maxX - minX, 0);
142
+ const height = Math.max(maxY - minY, 0);
143
+ if ((width * height) / (contentW * contentH) > 0.78) return null;
144
+ return {
145
+ left: minX - pad,
146
+ top: minY - pad,
147
+ width: width + pad * 2,
148
+ height: height + pad * 2,
149
+ };
150
+ }
151
+
152
+ export function mixSpot(
153
+ a: SpotRect | null,
154
+ b: SpotRect | null,
155
+ t: number,
156
+ ): { rect: SpotRect; opacity: number } | null {
157
+ const u = clamp01(t);
158
+ if (!a && !b) return null;
159
+ if (!a && b) return { rect: b, opacity: u };
160
+ if (a && !b) return { rect: a, opacity: 1 - u };
161
+ if (!a || !b) return null;
162
+ return {
163
+ rect: {
164
+ left: a.left + (b.left - a.left) * u,
165
+ top: a.top + (b.top - a.top) * u,
166
+ width: a.width + (b.width - a.width) * u,
167
+ height: a.height + (b.height - a.height) * u,
168
+ },
169
+ opacity: 1,
170
+ };
171
+ }
@@ -0,0 +1,90 @@
1
+ "use client";
2
+ import { useState } from "react";
3
+ import { Button } from "../../ui";
4
+
5
+ /**
6
+ * Docs code affordances: a hover-revealed copy button, a bare copyable code
7
+ * block, and a label-tabbed code switcher. Presentation-generic — samples
8
+ * arrive as `{ label, code }`, so language naming ("curl" vs "bash") is the
9
+ * caller's business. Token-backed utilities carry the look; the hover-reveal
10
+ * combinator for the copy button lives in styles/docs.css.
11
+ */
12
+
13
+ export interface CodeSample {
14
+ /** Tab label, e.g. "curl", "Python", "TypeScript". */
15
+ label: string;
16
+ code: string;
17
+ }
18
+
19
+ /** Tiny write-only "copy / copied" clipboard button (1.2s confirmation flip). */
20
+ function DocsCopyButton({ text, ariaLabel }: { text: string; ariaLabel: string }) {
21
+ const [copied, setCopied] = useState(false);
22
+ return (
23
+ <Button
24
+ type="button"
25
+ variant="ghost"
26
+ className="docs-code-copy h-auto cursor-pointer rounded-none border border-hair bg-surface/80 px-[0.4rem] py-[0.12rem] font-mono text-[0.7rem] font-normal text-ink-mute hover:bg-transparent hover:text-ink"
27
+ aria-label={ariaLabel}
28
+ onClick={() =>
29
+ navigator.clipboard?.writeText(text).then(
30
+ () => {
31
+ setCopied(true);
32
+ setTimeout(() => setCopied(false), 1200);
33
+ },
34
+ () => {},
35
+ )
36
+ }
37
+ >
38
+ {copied ? "copied" : "copy"}
39
+ </Button>
40
+ );
41
+ }
42
+
43
+ /** Pre-formatted code block with the hover-revealed copy affordance. */
44
+ export function DocsCodeBlock({
45
+ code,
46
+ copyLabel = "Copy code",
47
+ }: {
48
+ code: string;
49
+ copyLabel?: string;
50
+ }) {
51
+ return (
52
+ <div className="doc-code relative mt-[0.5rem]">
53
+ <DocsCopyButton text={code} ariaLabel={copyLabel} />
54
+ <pre className="m-0 overflow-x-auto rounded-none border border-hair bg-ink/6 px-[0.8rem] py-[0.7rem]">
55
+ <code className="whitespace-pre font-mono text-[0.8rem] text-ink">{code}</code>
56
+ </pre>
57
+ </div>
58
+ );
59
+ }
60
+
61
+ /** Label-tabbed code block — one sample visible at a time, copyable. */
62
+ export function CodeTabs({ samples }: { samples: CodeSample[] }) {
63
+ const [sel, setSel] = useState(0);
64
+ if (!samples.length) return null;
65
+ const cur = samples[Math.min(sel, samples.length - 1)];
66
+ return (
67
+ <div className="mt-[0.3rem] flex flex-col gap-[0.3rem]">
68
+ {samples.length > 1 && (
69
+ <div className="flex gap-[0.2rem]" role="tablist">
70
+ {samples.map((s, i) => (
71
+ <Button
72
+ key={s.label}
73
+ type="button"
74
+ role="tab"
75
+ aria-selected={i === sel}
76
+ variant="link"
77
+ className={`h-auto cursor-pointer rounded-none border border-transparent px-[0.6rem] py-[0.2rem] font-mono text-[0.72rem] font-normal transition-colors duration-150 ease-brand hover:no-underline ${
78
+ i === sel ? "bg-accent-weak text-ink" : "text-ink-mute hover:text-ink-soft"
79
+ }`}
80
+ onClick={() => setSel(i)}
81
+ >
82
+ {s.label}
83
+ </Button>
84
+ ))}
85
+ </div>
86
+ )}
87
+ <DocsCodeBlock code={cur.code} copyLabel={`Copy ${cur.label} example`} />
88
+ </div>
89
+ );
90
+ }
@@ -0,0 +1,151 @@
1
+ "use client";
2
+
3
+ import { useEffect, useRef, useState } from "react";
4
+ import { cn } from "../../lib/utils";
5
+ import type { CodeSample } from "./CodeTabs";
6
+
7
+ /**
8
+ * The install command, as the primary action. OpenCode-informed: a label tab
9
+ * strip sits on a hairline, and the command beneath it *is* the copy button —
10
+ * no separate "copy" chrome floating beside it. A click blurs the command and
11
+ * shows "copied" for a few seconds, then the command returns. The command is
12
+ * split into a muted `protocol` prefix and a medium-weight ink payload; there
13
+ * is no syntax colour. The inline form is the shared `copy-cmd` button the
14
+ * hero clone and the module compose line both use.
15
+ */
16
+ export interface CopyCommandSample extends CodeSample {
17
+ /** Leading token rendered muted, e.g. `docker compose` or `git clone`. */
18
+ protocol?: string;
19
+ }
20
+
21
+ export interface CopyCommandProps {
22
+ samples: CopyCommandSample[];
23
+ /** Accessible name for the tab strip. */
24
+ ariaLabel?: string;
25
+ className?: string;
26
+ /**
27
+ * Single-command form: no tab strip, no copy affordance — the whole code box
28
+ * *is* the button ("click the box and it copies"). For a hero where the
29
+ * command is a one-liner and the tabs would be noise. `samples[0]` is shown;
30
+ * the rest are ignored.
31
+ */
32
+ inline?: boolean;
33
+ }
34
+
35
+ /** How long the command stays blurred with the copied note. */
36
+ const COPIED_MS = 2500;
37
+
38
+ async function writeClipboard(text: string): Promise<boolean> {
39
+ const clipboard = typeof navigator === "undefined" ? undefined : navigator.clipboard;
40
+ if (clipboard?.writeText) {
41
+ try {
42
+ await clipboard.writeText(text);
43
+ return true;
44
+ } catch {
45
+ /* fall through */
46
+ }
47
+ }
48
+ try {
49
+ const el = document.createElement("textarea");
50
+ el.value = text;
51
+ el.setAttribute("readonly", "");
52
+ el.style.position = "fixed";
53
+ el.style.top = "-9999px";
54
+ document.body.appendChild(el);
55
+ el.select();
56
+ const ok = document.execCommand("copy");
57
+ document.body.removeChild(el);
58
+ return ok;
59
+ } catch {
60
+ return false;
61
+ }
62
+ }
63
+
64
+ function splitCommand(sample: CopyCommandSample): [string, string] {
65
+ const { code, protocol } = sample;
66
+ if (!protocol || !code.startsWith(protocol)) return ["", code];
67
+ return [protocol, code.slice(protocol.length)];
68
+ }
69
+
70
+ export function CopyCommand({ samples, ariaLabel = "Install command", className, inline = false }: CopyCommandProps) {
71
+ const [selected, setSelected] = useState(0);
72
+ const [copied, setCopied] = useState(false);
73
+ const timer = useRef<ReturnType<typeof setTimeout> | null>(null);
74
+
75
+ useEffect(
76
+ () => () => {
77
+ if (timer.current) clearTimeout(timer.current);
78
+ },
79
+ [],
80
+ );
81
+
82
+ const current = samples[Math.min(selected, samples.length - 1)];
83
+ if (!current) return null;
84
+
85
+ const [protocol, payload] = splitCommand(current);
86
+
87
+ function copy() {
88
+ void writeClipboard(current.code).then((ok) => {
89
+ if (!ok) return;
90
+ setCopied(true);
91
+ if (timer.current) clearTimeout(timer.current);
92
+ timer.current = setTimeout(() => setCopied(false), COPIED_MS);
93
+ });
94
+ }
95
+
96
+ return (
97
+ <div className={cn(inline ? "w-fit max-w-full" : "w-full max-w-[var(--measure-prose)]", className)}>
98
+ {inline ? null : (
99
+ <div
100
+ role="tablist"
101
+ aria-label={ariaLabel}
102
+ className="flex flex-wrap items-end gap-[1.4rem] border-b border-hair"
103
+ >
104
+ {samples.map((sample, index) => (
105
+ <button
106
+ key={sample.label}
107
+ type="button"
108
+ role="tab"
109
+ aria-selected={index === selected}
110
+ onClick={() => setSelected(index)}
111
+ className={cn(
112
+ "cursor-pointer border-b-2 border-transparent bg-transparent pb-[0.55rem] font-mono text-[0.8rem] tracking-[0.02em] transition-colors duration-150 ease-brand",
113
+ index === selected ? "border-b-ink text-ink" : "text-ink-mute hover:text-ink-soft",
114
+ )}
115
+ >
116
+ {sample.label}
117
+ </button>
118
+ ))}
119
+ </div>
120
+ )}
121
+ <button
122
+ type="button"
123
+ data-copied={copied ? "true" : "false"}
124
+ onClick={copy}
125
+ aria-label={`Copy ${inline ? "command" : `${current.label} command`}`}
126
+ className={cn(
127
+ "copy-cmd group relative flex cursor-pointer items-center gap-[1rem] px-[1rem] py-[0.85rem] text-left font-mono text-[0.85rem] leading-[1.5] text-ink hover:bg-surface-2",
128
+ inline
129
+ ? "w-auto max-w-full border border-hair bg-transparent"
130
+ : "w-full border-x border-b border-t-0 border-hair bg-surface",
131
+ )}
132
+ >
133
+ <span
134
+ className={cn(
135
+ "min-w-0 truncate whitespace-nowrap transition-[filter,opacity] duration-200 group-data-[copied=true]:blur-[3px] group-data-[copied=true]:opacity-40",
136
+ inline ? "" : "flex-1",
137
+ )}
138
+ >
139
+ {protocol ? <span className="text-ink-mute">{protocol}</span> : null}
140
+ <span className="font-medium">{payload}</span>
141
+ </span>
142
+ <span
143
+ aria-hidden="true"
144
+ className="pointer-events-none absolute inset-0 hidden items-center justify-center font-medium text-ink group-data-[copied=true]:flex"
145
+ >
146
+ copied
147
+ </span>
148
+ </button>
149
+ </div>
150
+ );
151
+ }