@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,88 @@
1
+ "use client";
2
+
3
+ import { useState, type MouseEventHandler, type ReactNode } from "react";
4
+ import { Button } from "../../ui";
5
+
6
+ /**
7
+ * AnnouncementBar — Graphite's announcement bar promoted from the design
8
+ * reference (chrome/announcement-bar): a 48px strip above the nav where the
9
+ * ENTIRE bar is one click target, plus a ✕ that dismisses it. An a11y region.
10
+ * Content-gated by doctrine: it ships with real news (a release, a launch),
11
+ * never evergreen marketing — the component takes the news line and CTA via
12
+ * props and carries no copy of its own.
13
+ *
14
+ * Dismissal: internal state by default; pass `dismissed` to control it, and
15
+ * persist however the app likes in `onDismiss` (localStorage, a cookie …) —
16
+ * persistence is deliberately consumer-owned.
17
+ *
18
+ * The frame (.annc-bar) sits in @layer components so call-site utilities can
19
+ * retune it (production strips the demo radius with rounded-none, etc.);
20
+ * .annc-link keeps its class for the parent-hover-descendant CTA underline.
21
+ *
22
+ * Wiring (in the consuming app):
23
+ * globals.css @import "@digithings-ai/ui/styles/effects-chrome.css";
24
+ * @source "<path-to>/packages/ui/src/components/effects-chrome";
25
+ */
26
+ export type AnnouncementBarProps = {
27
+ /** Destination — the entire bar is the link. */
28
+ href: string;
29
+ /** The news line — "digiquant v2.4 ships kelly-capped position sizing." */
30
+ children: ReactNode;
31
+ /** Accented bold call-to-action — "Read the changelog →". */
32
+ cta?: ReactNode;
33
+ /** Controlled dismissal; omit to let the bar own its state. */
34
+ dismissed?: boolean;
35
+ /** Uncontrolled initial state — e.g. hydrated from storage. */
36
+ defaultDismissed?: boolean;
37
+ /** Fired when the ✕ is pressed (persist it here). */
38
+ onDismiss?: () => void;
39
+ /** Render the ✕ at all. */
40
+ dismissible?: boolean;
41
+ /** Anchor click passthrough (SPA routing, analytics, demo preventDefault). */
42
+ onLinkClick?: MouseEventHandler<HTMLAnchorElement>;
43
+ ariaLabel?: string;
44
+ className?: string;
45
+ };
46
+
47
+ export function AnnouncementBar({
48
+ href,
49
+ children,
50
+ cta,
51
+ dismissed,
52
+ defaultDismissed = false,
53
+ onDismiss,
54
+ dismissible = true,
55
+ onLinkClick,
56
+ ariaLabel = "Announcement",
57
+ className,
58
+ }: AnnouncementBarProps) {
59
+ const [selfDismissed, setSelfDismissed] = useState(defaultDismissed);
60
+ if (dismissed ?? selfDismissed) return null;
61
+
62
+ return (
63
+ <div
64
+ className={`annc-bar${className ? ` ${className}` : ""}`}
65
+ role="region"
66
+ aria-label={ariaLabel}
67
+ >
68
+ <a href={href} className="annc-link" onClick={onLinkClick}>
69
+ <span>{children}</span>
70
+ {cta ? <b>{cta}</b> : null}
71
+ </a>
72
+ {dismissible ? (
73
+ <Button
74
+ type="button"
75
+ variant="ghost"
76
+ className="me-[0.3rem] h-10 w-10 flex-shrink-0 cursor-pointer border-0 bg-transparent text-[0.8rem] font-normal text-ink-mute hover:bg-transparent hover:text-ink"
77
+ aria-label="Dismiss announcement"
78
+ onClick={() => {
79
+ setSelfDismissed(true);
80
+ onDismiss?.();
81
+ }}
82
+ >
83
+ ✕
84
+ </Button>
85
+ ) : null}
86
+ </div>
87
+ );
88
+ }
@@ -0,0 +1,461 @@
1
+ "use client";
2
+
3
+ import {
4
+ Children,
5
+ createContext,
6
+ useCallback,
7
+ useContext,
8
+ useEffect,
9
+ useLayoutEffect,
10
+ useMemo,
11
+ useRef,
12
+ useState,
13
+ type ReactNode,
14
+ type RefObject,
15
+ } from "react";
16
+ import { useMotionValue, useMotionValueEvent, useScroll, useTransform, type MotionValue } from "motion/react";
17
+ import { m, useMotionSafe } from "../../motion/primitives";
18
+ import { cn } from "../../lib/utils";
19
+ import {
20
+ TRACK_MIN_WIDTH,
21
+ clamp,
22
+ focusTargetX,
23
+ nearestIndex,
24
+ runwayHeight,
25
+ scrollTopForX,
26
+ trackTravel,
27
+ } from "./horizontal-track-core";
28
+
29
+ /**
30
+ * HorizontalScrollTrack — a row of cards that a vertical scroll drives sideways.
31
+ *
32
+ * Wide + motion allowed (>= `minWidth`, default 960px): a `position: sticky`
33
+ * pin holds the row while the page scrolls; the runway's height is DERIVED from
34
+ * the track's content width (pin height + horizontal travel, re-measured by
35
+ * ResizeObserver), so 1px of scroll is 1px of travel and the next section can
36
+ * never overlap the pin. The translate is a motion-value function transform of
37
+ * scroll progress — written straight to the element, no per-frame setState.
38
+ *
39
+ * Narrow, reduced-motion, first paint, and no-JS: a native scroll-snap strip
40
+ * with every card reachable (the honest fallback, never a hidden state).
41
+ *
42
+ * Accessibility: each child is wrapped in a `role="listitem"` tab stop in DOM
43
+ * order. Focusing an off-screen card scrolls the runway to it (pinned) or lets
44
+ * the browser scroll the strip (native). The pinned viewport is `overflow-x:
45
+ * clip`, never a scroll container, so focus cannot skew the transform.
46
+ *
47
+ * Progress is exposed two ways: `header` / `footer` may be render props that
48
+ * receive the state, and `useHorizontalTrack()` reads it from context (used by
49
+ * `HorizontalTrackStepper`). `progress` is a MotionValue (0..1) so a consumer
50
+ * animates from it with transforms and never re-renders per frame; `activeIndex`
51
+ * is React state that only changes when the leading card changes.
52
+ *
53
+ * One pin per page (plan section 2). Wiring (in the consuming app):
54
+ * globals.css @source "<path-to>/packages/ui/src/components/effects-chrome";
55
+ * No CSS sheet — everything is utilities. Requires MotionProvider (`m.*`).
56
+ */
57
+ export type HorizontalTrackState = {
58
+ /** Scroll progress through the track, 0..1 (a MotionValue, not React state). */
59
+ progress: MotionValue<number>;
60
+ /** Index of the card at the leading edge; changes only at card boundaries. */
61
+ activeIndex: number;
62
+ /** Number of cards. */
63
+ count: number;
64
+ /** True while the sticky pin is active; false for the native snap strip. */
65
+ pinned: boolean;
66
+ /** Bring card `index` to the leading edge (scrolls the runway or the strip). */
67
+ goTo: (index: number) => void;
68
+ };
69
+
70
+ export type HorizontalScrollTrackProps = {
71
+ /** The cards. Each direct child becomes one tab-stop track item. */
72
+ children: ReactNode;
73
+ /** Accessible name for the list of cards. */
74
+ ariaLabel: string;
75
+ /** Content above the row (inside the pin). Node or render prop. */
76
+ header?: ReactNode | ((state: HorizontalTrackState) => ReactNode);
77
+ /** Content below the row (inside the pin) — e.g. a HorizontalTrackStepper. */
78
+ footer?: ReactNode | ((state: HorizontalTrackState) => ReactNode);
79
+ /** Classes on each track item wrapper (sets the card width). */
80
+ itemClassName?: string;
81
+ /** Classes on the outermost element. */
82
+ className?: string;
83
+ /** Sticky offset in px — set to the height of chrome pinned above (nav). */
84
+ pinTop?: number;
85
+ /** Pin only at this viewport width and up. Default 960. */
86
+ minWidth?: number;
87
+ };
88
+
89
+ const TrackContext = createContext<HorizontalTrackState | null>(null);
90
+
91
+ /** Read the track's progress state from a descendant (header / footer / card). */
92
+ export function useHorizontalTrack(): HorizontalTrackState {
93
+ const state = useContext(TrackContext);
94
+ if (!state) throw new Error("useHorizontalTrack must be used inside <HorizontalScrollTrack>");
95
+ return state;
96
+ }
97
+
98
+ const useIsoLayoutEffect = typeof document !== "undefined" ? useLayoutEffect : useEffect;
99
+
100
+ function useMinWidth(px: number): boolean {
101
+ const [matches, setMatches] = useState(false);
102
+ useEffect(() => {
103
+ if (typeof matchMedia !== "function") return;
104
+ const mq = matchMedia(`(min-width: ${px}px)`);
105
+ const apply = () => setMatches(mq.matches);
106
+ apply();
107
+ mq.addEventListener("change", apply);
108
+ return () => mq.removeEventListener("change", apply);
109
+ }, [px]);
110
+ return matches;
111
+ }
112
+
113
+ /** Leading-edge offset of each item, relative to the first (padding-agnostic). */
114
+ function readOffsets(track: HTMLElement): number[] {
115
+ const kids = Array.from(track.children).filter((c): c is HTMLElement => c instanceof HTMLElement);
116
+ const base = kids[0]?.offsetLeft ?? 0;
117
+ return kids.map((k) => k.offsetLeft - base);
118
+ }
119
+
120
+ function slot(
121
+ node: HorizontalTrackProps["header"],
122
+ state: HorizontalTrackState,
123
+ ): ReactNode {
124
+ return typeof node === "function" ? node(state) : node;
125
+ }
126
+ type HorizontalTrackProps = HorizontalScrollTrackProps;
127
+
128
+ // Edge fade for the row: alpha stops through a token (mask uses alpha only), so
129
+ // no raw colour. 1.5rem matches the track's inline padding so the first card at
130
+ // rest is fully opaque.
131
+ const FADE =
132
+ "[mask-image:linear-gradient(90deg,transparent,var(--ink)_1.5rem,var(--ink)_calc(100%_-_1.5rem),transparent)]";
133
+
134
+ const ITEM_FOCUS =
135
+ "focus-visible:outline focus-visible:outline-1 focus-visible:outline-offset-2 focus-visible:outline-accent";
136
+ const ITEM_DEFAULT_WIDTH = "w-[min(82vw,22rem)]";
137
+
138
+ type BodyProps = {
139
+ items: ReactNode[];
140
+ ariaLabel: string;
141
+ header: HorizontalTrackProps["header"];
142
+ footer: HorizontalTrackProps["footer"];
143
+ itemClassName?: string;
144
+ className?: string;
145
+ progress: MotionValue<number>;
146
+ state: HorizontalTrackState;
147
+ setActive: (i: number) => void;
148
+ goToRef: RefObject<(i: number) => void>;
149
+ };
150
+
151
+ function Items({
152
+ items,
153
+ itemClassName,
154
+ onItemFocus,
155
+ }: {
156
+ items: ReactNode[];
157
+ itemClassName?: string;
158
+ onItemFocus?: (el: HTMLElement) => void;
159
+ }) {
160
+ return items.map((child, i) => (
161
+ <div
162
+ key={i}
163
+ role="listitem"
164
+ tabIndex={0}
165
+ data-track-item=""
166
+ className={cn("flex-none snap-start", ITEM_DEFAULT_WIDTH, ITEM_FOCUS, itemClassName)}
167
+ onFocus={(e) => onItemFocus?.(e.currentTarget)}
168
+ >
169
+ {child}
170
+ </div>
171
+ ));
172
+ }
173
+
174
+ /** Native scroll-snap strip: the default, the narrow, and the reduced-motion render. */
175
+ function StripBody({
176
+ items,
177
+ ariaLabel,
178
+ header,
179
+ footer,
180
+ itemClassName,
181
+ className,
182
+ progress,
183
+ state,
184
+ setActive,
185
+ goToRef,
186
+ }: BodyProps) {
187
+ const stripRef = useRef<HTMLDivElement | null>(null);
188
+ const safe = useMotionSafe();
189
+ const lastIndex = useRef(0);
190
+
191
+ const onScroll = useCallback(() => {
192
+ const el = stripRef.current;
193
+ if (!el) return;
194
+ const range = el.scrollWidth - el.clientWidth;
195
+ progress.set(range > 0 ? clamp(el.scrollLeft / range, 0, 1) : 0);
196
+ const idx = nearestIndex(readOffsets(el), el.scrollLeft);
197
+ if (idx !== lastIndex.current) {
198
+ lastIndex.current = idx;
199
+ setActive(idx);
200
+ }
201
+ }, [progress, setActive]);
202
+
203
+ useEffect(() => {
204
+ goToRef.current = (index) => {
205
+ const el = stripRef.current;
206
+ if (!el) return;
207
+ const offsets = readOffsets(el);
208
+ const left = offsets[clamp(index, 0, offsets.length - 1)] ?? 0;
209
+ el.scrollTo({ left, behavior: safe ? "smooth" : "auto" });
210
+ };
211
+ progress.set(0);
212
+ // A strip mounts at scrollLeft 0; drop any index carried over from the pinned body.
213
+ lastIndex.current = 0;
214
+ setActive(0);
215
+ }, [goToRef, progress, safe, setActive]);
216
+
217
+ return (
218
+ <div className={cn("relative", className)} data-track="strip">
219
+ {slot(header, state)}
220
+ <div className={FADE}>
221
+ <div
222
+ ref={stripRef}
223
+ role="list"
224
+ aria-label={ariaLabel}
225
+ onScroll={onScroll}
226
+ className="relative flex snap-x snap-mandatory gap-4 overflow-x-auto scroll-ps-6 px-6 pb-3 [scrollbar-width:thin]"
227
+ >
228
+ <Items items={items} itemClassName={itemClassName} />
229
+ </div>
230
+ </div>
231
+ {slot(footer, state)}
232
+ </div>
233
+ );
234
+ }
235
+
236
+ /** Sticky pin: runway height from content width, translate from scroll progress. */
237
+ function PinnedBody({
238
+ items,
239
+ ariaLabel,
240
+ header,
241
+ footer,
242
+ itemClassName,
243
+ className,
244
+ progress,
245
+ state,
246
+ setActive,
247
+ goToRef,
248
+ pinTop,
249
+ }: BodyProps & { pinTop: number }) {
250
+ const runwayRef = useRef<HTMLDivElement | null>(null);
251
+ const pinRef = useRef<HTMLDivElement | null>(null);
252
+ const viewportRef = useRef<HTMLDivElement | null>(null);
253
+ const trackRef = useRef<HTMLDivElement | null>(null);
254
+ const offsets = useRef<number[]>([]);
255
+ const lastIndex = useRef(0);
256
+ const travel = useMotionValue(0);
257
+ const [runwayPx, setRunwayPx] = useState<number | undefined>(undefined);
258
+
259
+ const { scrollYProgress } = useScroll({
260
+ target: runwayRef,
261
+ offset: [`start ${pinTop}px` as `start ${number}px`, "end end"],
262
+ });
263
+ // Function transform: scroll progress and measured travel in, translateX out.
264
+ const x = useTransform([scrollYProgress, travel], ([p, t]: number[]) => -clamp(p, 0, 1) * t);
265
+
266
+ useMotionValueEvent(scrollYProgress, "change", (p) => {
267
+ progress.set(p);
268
+ const idx = nearestIndex(offsets.current, clamp(p, 0, 1) * travel.get());
269
+ if (idx !== lastIndex.current) {
270
+ lastIndex.current = idx;
271
+ setActive(idx);
272
+ }
273
+ });
274
+
275
+ const measure = useCallback(() => {
276
+ const track = trackRef.current;
277
+ const viewport = viewportRef.current;
278
+ const pin = pinRef.current;
279
+ if (!track || !viewport || !pin) return;
280
+ const t = trackTravel(track.scrollWidth, viewport.clientWidth);
281
+ offsets.current = readOffsets(track);
282
+ travel.set(t);
283
+ setRunwayPx(runwayHeight(pin.clientHeight, t));
284
+ }, [travel]);
285
+
286
+ useIsoLayoutEffect(() => {
287
+ measure();
288
+ progress.set(scrollYProgress.get());
289
+ if (typeof ResizeObserver === "undefined") return;
290
+ const observer = new ResizeObserver(measure);
291
+ for (const el of [trackRef.current, viewportRef.current, pinRef.current]) {
292
+ if (el) observer.observe(el);
293
+ }
294
+ return () => observer.disconnect();
295
+ }, [measure, progress, scrollYProgress]);
296
+
297
+ const scrollToX = useCallback(
298
+ (target: number, behavior: ScrollBehavior) => {
299
+ const runway = runwayRef.current;
300
+ if (!runway) return;
301
+ window.scrollTo({
302
+ top: scrollTopForX({
303
+ runwayTopInDocument: runway.getBoundingClientRect().top + window.scrollY,
304
+ pinTop,
305
+ x: target,
306
+ travel: travel.get(),
307
+ }),
308
+ behavior,
309
+ });
310
+ },
311
+ [pinTop, travel],
312
+ );
313
+
314
+ useEffect(() => {
315
+ goToRef.current = (index) => {
316
+ const list = offsets.current;
317
+ if (list.length === 0) return;
318
+ scrollToX(list[clamp(index, 0, list.length - 1)] ?? 0, "smooth");
319
+ };
320
+ }, [goToRef, scrollToX]);
321
+
322
+ /** Focus moved to a card: reveal it by scrolling the runway (instant, so the
323
+ * focus ring never chases a smooth scroll). */
324
+ const onItemFocus = useCallback(
325
+ (el: HTMLElement) => {
326
+ const viewport = viewportRef.current;
327
+ const track = trackRef.current;
328
+ if (!viewport || !track) return;
329
+ const base = (track.children[0] as HTMLElement | undefined)?.offsetLeft ?? 0;
330
+ const target = focusTargetX({
331
+ itemLeft: el.offsetLeft - base,
332
+ itemWidth: el.offsetWidth,
333
+ currentX: -x.get(),
334
+ viewportWidth: viewport.clientWidth - 2 * base,
335
+ travel: travel.get(),
336
+ });
337
+ if (target !== null) scrollToX(target, "auto");
338
+ },
339
+ [scrollToX, travel, x],
340
+ );
341
+
342
+ return (
343
+ <div
344
+ ref={runwayRef}
345
+ className={cn("relative", className)}
346
+ style={{ height: runwayPx }}
347
+ data-track="pinned"
348
+ >
349
+ <div
350
+ ref={pinRef}
351
+ className="sticky flex flex-col justify-center gap-6 overflow-x-clip"
352
+ style={{ top: pinTop, height: `calc(100svh - ${pinTop}px)` }}
353
+ >
354
+ {slot(header, state)}
355
+ <div ref={viewportRef} className={cn("overflow-x-clip", FADE)}>
356
+ <m.div
357
+ ref={trackRef}
358
+ role="list"
359
+ aria-label={ariaLabel}
360
+ style={{ x }}
361
+ className="relative flex w-max gap-4 px-6 will-change-transform"
362
+ >
363
+ <Items items={items} itemClassName={itemClassName} onItemFocus={onItemFocus} />
364
+ </m.div>
365
+ </div>
366
+ {slot(footer, state)}
367
+ </div>
368
+ </div>
369
+ );
370
+ }
371
+
372
+ export function HorizontalScrollTrack({
373
+ children,
374
+ ariaLabel,
375
+ header,
376
+ footer,
377
+ itemClassName,
378
+ className,
379
+ pinTop = 0,
380
+ minWidth = TRACK_MIN_WIDTH,
381
+ }: HorizontalScrollTrackProps) {
382
+ const items = Children.toArray(children);
383
+ const safe = useMotionSafe();
384
+ const wide = useMinWidth(minWidth);
385
+ const pinned = safe && wide;
386
+ const progress = useMotionValue(0);
387
+ const [activeIndex, setActiveIndex] = useState(0);
388
+ const goToRef = useRef<(index: number) => void>(() => {});
389
+ const goTo = useCallback((index: number) => goToRef.current(index), []);
390
+ const count = items.length;
391
+
392
+ const state = useMemo<HorizontalTrackState>(
393
+ () => ({ progress, activeIndex, count, pinned, goTo }),
394
+ [progress, activeIndex, count, pinned, goTo],
395
+ );
396
+
397
+ const body: BodyProps = {
398
+ items,
399
+ ariaLabel,
400
+ header,
401
+ footer,
402
+ itemClassName,
403
+ className,
404
+ progress,
405
+ state,
406
+ setActive: setActiveIndex,
407
+ goToRef,
408
+ };
409
+
410
+ return (
411
+ <TrackContext.Provider value={state}>
412
+ {pinned ? <PinnedBody {...body} pinTop={pinTop} /> : <StripBody {...body} />}
413
+ </TrackContext.Provider>
414
+ );
415
+ }
416
+
417
+ /**
418
+ * HorizontalTrackStepper — the stage stepper for a track: a hairline rail whose
419
+ * fill scales with progress (transform only) over one button per card that
420
+ * highlights the active card and jumps to it. Reads the track from context, so
421
+ * it belongs in the track's `header` / `footer`. Labels are plain text, so the
422
+ * stepper reads with no JS (the buttons simply do nothing then).
423
+ */
424
+ export function HorizontalTrackStepper({
425
+ labels,
426
+ className,
427
+ }: {
428
+ /** One label per card, in card order. */
429
+ labels: string[];
430
+ className?: string;
431
+ }) {
432
+ const { progress, activeIndex, goTo } = useHorizontalTrack();
433
+ return (
434
+ <div className={cn("grid gap-3 px-6", className)}>
435
+ <div className="h-px bg-hair" aria-hidden="true">
436
+ <m.div
437
+ className="h-px origin-left bg-accent rtl:origin-right"
438
+ style={{ scaleX: progress }}
439
+ />
440
+ </div>
441
+ <ol className="m-0 flex list-none flex-wrap gap-x-5 gap-y-2 p-0 font-mono text-[0.68rem] tracking-[0.04em]">
442
+ {labels.map((label, i) => (
443
+ <li key={label}>
444
+ <button
445
+ type="button"
446
+ onClick={() => goTo(i)}
447
+ aria-current={i === activeIndex ? "step" : undefined}
448
+ className={cn(
449
+ "cursor-pointer bg-transparent p-0 font-[inherit] transition-colors hover:text-ink focus-visible:outline focus-visible:outline-1 focus-visible:outline-offset-2 focus-visible:outline-accent motion-reduce:transition-none",
450
+ i === activeIndex ? "text-ink" : "text-ink-mute",
451
+ )}
452
+ >
453
+ <span aria-hidden="true">{i === activeIndex ? "▸" : String(i + 1).padStart(2, "0")}</span>{" "}
454
+ {label}
455
+ </button>
456
+ </li>
457
+ ))}
458
+ </ol>
459
+ </div>
460
+ );
461
+ }