@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.
- package/README.md +48 -2
- package/package.json +143 -4
- package/src/components/NavShell.tsx +776 -0
- package/src/components/SocialRow.tsx +87 -0
- package/src/components/Spinner.tsx +27 -0
- package/src/components/StackLogo.tsx +40 -0
- package/src/components/Terminal.tsx +110 -0
- package/src/components/TerminalManifest.tsx +212 -0
- package/src/components/ThemeProvider.tsx +271 -0
- package/src/components/account/AuthCard.tsx +309 -0
- package/src/components/assistant-ui/elements/mermaid-diagram.aui.tsx +63 -0
- package/src/components/assistant-ui/elements/mermaid-diagram.tsx +340 -0
- package/src/components/brand/BrandWordmark.tsx +32 -0
- package/src/components/changelog/ReleaseRail.tsx +81 -0
- package/src/components/changelog/index.ts +1 -0
- package/src/components/chat/ChatCodeBlock.tsx +67 -0
- package/src/components/chat/ChatMarkdown.tsx +66 -0
- package/src/components/chat/ChatMarkdownSource.tsx +203 -0
- package/src/components/chat/ChatMermaidBlock.tsx +202 -0
- package/src/components/chat/ChatMessage.tsx +77 -0
- package/src/components/chat/ChatPlayback.tsx +422 -0
- package/src/components/chat/ChatStreamCursor.tsx +25 -0
- package/src/components/chat/ChatThinking.tsx +113 -0
- package/src/components/chat/ChatToolCall.tsx +190 -0
- package/src/components/chat/ChatTranscript.tsx +60 -0
- package/src/components/chat/ChatWidgetFrame.tsx +107 -0
- package/src/components/chat/DigichatBootLoader.tsx +683 -0
- package/src/components/chat/DigichatLauncher.tsx +361 -0
- package/src/components/chat/DigichatThread.tsx +118 -0
- package/src/components/chat/DigichatThreadList.tsx +54 -0
- package/src/components/chat/DigichatWordmark.tsx +24 -0
- package/src/components/chat/DotMatrix.tsx +650 -0
- package/src/components/chat/boot-lab.tsx +766 -0
- package/src/components/chat/boot-signal.ts +144 -0
- package/src/components/chat/chat-surfaces.ts +52 -0
- package/src/components/chat/connection-status.ts +40 -0
- package/src/components/chat/digichat-wordmark.ts +355 -0
- package/src/components/chat/disclosure-chevron.tsx +49 -0
- package/src/components/chat/followable-href.ts +9 -0
- package/src/components/chat/gallery-thread/action-icons.tsx +25 -0
- package/src/components/chat/gallery-thread/attachment.aui.tsx +262 -0
- package/src/components/chat/gallery-thread/block-caret.tsx +439 -0
- package/src/components/chat/gallery-thread/cn.ts +1 -0
- package/src/components/chat/gallery-thread/composer-keyboard.ts +106 -0
- package/src/components/chat/gallery-thread/composer-trigger-popover.aui.tsx +235 -0
- package/src/components/chat/gallery-thread/css.d.ts +6 -0
- package/src/components/chat/gallery-thread/file.tsx +238 -0
- package/src/components/chat/gallery-thread/follow-up-suggestions.aui.tsx +92 -0
- package/src/components/chat/gallery-thread/format-json-dump.ts +112 -0
- package/src/components/chat/gallery-thread/hooks/use-attachment-src.ts +46 -0
- package/src/components/chat/gallery-thread/hooks/use-copy-to-clipboard.ts +9 -0
- package/src/components/chat/gallery-thread/image.tsx +519 -0
- package/src/components/chat/gallery-thread/markdown-text.tsx +278 -0
- package/src/components/chat/gallery-thread/message-error.aui.tsx +105 -0
- package/src/components/chat/gallery-thread/message-timing.aui.tsx +143 -0
- package/src/components/chat/gallery-thread/private-work-step.tsx +188 -0
- package/src/components/chat/gallery-thread/reasoning.tsx +332 -0
- package/src/components/chat/gallery-thread/scroll-mode.ts +174 -0
- package/src/components/chat/gallery-thread/source.tsx +76 -0
- package/src/components/chat/gallery-thread/thread.aui.tsx +1619 -0
- package/src/components/chat/gallery-thread/tooltip-icon-button.tsx +53 -0
- package/src/components/chat/gallery-thread/typed-welcome-copy.tsx +18 -0
- package/src/components/chat/gallery-thread/ui/tooltip.tsx +22 -0
- package/src/components/chat/gallery-thread/work-chain-summary.tsx +48 -0
- package/src/components/chat/gallery-thread/work-chain.tsx +110 -0
- package/src/components/chat/gallery-thread/work-status.ts +66 -0
- package/src/components/chat/overlay-scrollbar.tsx +86 -0
- package/src/components/chat/sanitize-mermaid-svg.ts +38 -0
- package/src/components/chat/skins/SOURCE.md +36 -0
- package/src/components/chat/skins/base/attachment.aui.tsx +287 -0
- package/src/components/chat/skins/base/aui-default/README.md +45 -0
- package/src/components/chat/skins/base/aui-default/collapsible-root.tsx +19 -0
- package/src/components/chat/skins/base/aui-default/collapsible.tsx +21 -0
- package/src/components/chat/skins/base/aui-default/reasoning.aui.tsx +120 -0
- package/src/components/chat/skins/base/aui-default/reasoning.tsx +331 -0
- package/src/components/chat/skins/base/aui-default/surfaces.ts +7 -0
- package/src/components/chat/skins/base/aui-default/tool-fallback.aui.tsx +772 -0
- package/src/components/chat/skins/base/aui-default/tool-group.aui.tsx +241 -0
- package/src/components/chat/skins/base/docs-model-options.ts +6 -0
- package/src/components/chat/skins/base/elements/assistant-trace.aui.tsx +169 -0
- package/src/components/chat/skins/base/elements/composer-trigger-popover.aui.tsx +271 -0
- package/src/components/chat/skins/base/elements/directive-text.aui.tsx +29 -0
- package/src/components/chat/skins/base/elements/directive-text.tsx +76 -0
- package/src/components/chat/skins/base/elements/message-timing.aui.tsx +104 -0
- package/src/components/chat/skins/base/elements/model-selector.aui.tsx +140 -0
- package/src/components/chat/skins/base/elements/model-selector.tsx +720 -0
- package/src/components/chat/skins/base/elements/quote.aui.tsx +290 -0
- package/src/components/chat/skins/base/elements/work-chain.tsx +100 -0
- package/src/components/chat/skins/base/markdown-text.tsx +294 -0
- package/src/components/chat/skins/base/model.ts +1 -0
- package/src/components/chat/skins/base/thread.tsx +1050 -0
- package/src/components/chat/skins/base/tooltip-icon-button.tsx +56 -0
- package/src/components/chat/skins/base/ui/avatar.tsx +115 -0
- package/src/components/chat/skins/base/ui/badge.tsx +51 -0
- package/src/components/chat/skins/base/ui/button.tsx +67 -0
- package/src/components/chat/skins/base/ui/command.tsx +185 -0
- package/src/components/chat/skins/base/ui/dialog.tsx +168 -0
- package/src/components/chat/skins/base/ui/dot-matrix.tsx +267 -0
- package/src/components/chat/skins/base/ui/popover.tsx +97 -0
- package/src/components/chat/skins/base/ui/skeleton.tsx +20 -0
- package/src/components/chat/skins/base/ui/textarea.tsx +24 -0
- package/src/components/chat/skins/base/ui/tooltip.tsx +73 -0
- package/src/components/chat/skins/base-assistant-ui/config-provider.tsx +90 -0
- package/src/components/chat/skins/base-assistant-ui/defaults.ts +419 -0
- package/src/components/chat/skins/base-assistant-ui/index.tsx +44 -0
- package/src/components/chat/skins/chatgpt.tsx +448 -0
- package/src/components/chat/skins/claude.tsx +380 -0
- package/src/components/chat/skins/digichat-composer-toolbar.tsx +151 -0
- package/src/components/chat/skins/digichat-context-meter-shares.ts +158 -0
- package/src/components/chat/skins/digichat-context-meter.tsx +243 -0
- package/src/components/chat/skins/digichat-meter-tone.ts +26 -0
- package/src/components/chat/skins/digichat-usage-meter.tsx +103 -0
- package/src/components/chat/skins/digichat.tsx +144 -0
- package/src/components/chat/skins/expo-react-native.tsx +29 -0
- package/src/components/chat/skins/gemini.tsx +322 -0
- package/src/components/chat/skins/grok-icon.tsx +34 -0
- package/src/components/chat/skins/grok.tsx +369 -0
- package/src/components/chat/skins/index.ts +46 -0
- package/src/components/chat/skins/perplexity.tsx +462 -0
- package/src/components/chat/skins/product-page-assistant/assistant-modal.tsx +73 -0
- package/src/components/chat/skins/product-page-assistant/index.tsx +19 -0
- package/src/components/chat/skins/product-page-assistant/product-dashboard.tsx +164 -0
- package/src/components/chat/skins/react-ink.tsx +97 -0
- package/src/components/chat/skins/thread-skin-host-contract.ts +102 -0
- package/src/components/chat/skins/thread-skin-view.tsx +156 -0
- package/src/components/chat/skins/thread-skins.ts +143 -0
- package/src/components/chat/skins/tooltip-icon-button.tsx +35 -0
- package/src/components/chat/skins/ui/dropdown-menu.tsx +272 -0
- package/src/components/chat/skins/webpage-assistant/assistant-config.ts +238 -0
- package/src/components/chat/skins/webpage-assistant/assistant-modal.tsx +78 -0
- package/src/components/chat/skins/webpage-assistant/config-provider.tsx +132 -0
- package/src/components/chat/skins/webpage-assistant/docs-app.tsx +106 -0
- package/src/components/chat/skins/webpage-assistant/docs-article.tsx +109 -0
- package/src/components/chat/skins/webpage-assistant/docs-assistant.tsx +73 -0
- package/src/components/chat/skins/webpage-assistant/docs-nav.tsx +81 -0
- package/src/components/chat/skins/webpage-assistant/host-ui.ts +79 -0
- package/src/components/chat/skins/webpage-assistant/index.tsx +12 -0
- package/src/components/chat/skins/webpage-assistant/markdown-page.tsx +11 -0
- package/src/components/chat/skins/webpage-assistant/pages.ts +9 -0
- package/src/components/chat/skins/webpage-assistant/types.ts +178 -0
- package/src/components/chat/skins/webpage-assistant/use-mobile.ts +19 -0
- package/src/components/chat/stock/SOURCE.md +90 -0
- package/src/components/chat/stock/artifacts-panel.tsx +305 -0
- package/src/components/chat/stock/attachment.aui.tsx +280 -0
- package/src/components/chat/stock/chat-notice.tsx +143 -0
- package/src/components/chat/stock/chat-private-mode.tsx +64 -0
- package/src/components/chat/stock/deploy-ui-context.tsx +89 -0
- package/src/components/chat/stock/embed-chat-prefs.tsx +471 -0
- package/src/components/chat/stock/file.tsx +265 -0
- package/src/components/chat/stock/follow-up-suggestions.aui.tsx +83 -0
- package/src/components/chat/stock/image.tsx +531 -0
- package/src/components/chat/stock/index.ts +129 -0
- package/src/components/chat/stock/json-view.tsx +140 -0
- package/src/components/chat/stock/markdown-text.tsx +319 -0
- package/src/components/chat/stock/message-error.aui.tsx +96 -0
- package/src/components/chat/stock/message-export-menu.tsx +156 -0
- package/src/components/chat/stock/palette-row-preview.tsx +26 -0
- package/src/components/chat/stock/reasoning.aui.tsx +120 -0
- package/src/components/chat/stock/reasoning.tsx +335 -0
- package/src/components/chat/stock/skin-chrome.tsx +81 -0
- package/src/components/chat/stock/skin-credit.tsx +63 -0
- package/src/components/chat/stock/skin-palette-bridge.tsx +235 -0
- package/src/components/chat/stock/skin-palette-context.ts +16 -0
- package/src/components/chat/stock/skin-runtime.tsx +52 -0
- package/src/components/chat/stock/skin-slash-palette.tsx +393 -0
- package/src/components/chat/stock/skin-types.ts +217 -0
- package/src/components/chat/stock/stock-chat-prefs-host.tsx +501 -0
- package/src/components/chat/stock/stock-send-gate.tsx +94 -0
- package/src/components/chat/stock/stock-shape.tsx +39 -0
- package/src/components/chat/stock/thread.aui.tsx +645 -0
- package/src/components/chat/stock/tool-display.ts +6 -0
- package/src/components/chat/stock/tool-fallback.aui.tsx +866 -0
- package/src/components/chat/stock/tool-group.aui.tsx +244 -0
- package/src/components/chat/stock/tool-surface.ts +120 -0
- package/src/components/chat/stock/tooltip-icon-button.tsx +49 -0
- package/src/components/chat/stock/ui/avatar.tsx +108 -0
- package/src/components/chat/stock/ui/button.tsx +81 -0
- package/src/components/chat/stock/ui/collapsible.tsx +21 -0
- package/src/components/chat/stock/ui/dialog.tsx +163 -0
- package/src/components/chat/stock/ui/skeleton.tsx +13 -0
- package/src/components/chat/stock/ui/textarea.tsx +20 -0
- package/src/components/chat/stock/ui/tooltip.tsx +68 -0
- package/src/components/chat/stock/use-attachment-src.ts +42 -0
- package/src/components/chat/stock/use-copy-to-clipboard.ts +29 -0
- package/src/components/chat/strip-model-boilerplate.ts +42 -0
- package/src/components/chat/suggestion-cycle.tsx +230 -0
- package/src/components/chat/suggestion-scroll-row.tsx +81 -0
- package/src/components/chat/trace-grouping/chain-group-by.ts +44 -0
- package/src/components/chat/trace-grouping/chain-state.ts +141 -0
- package/src/components/chat/trace-grouping/index.ts +32 -0
- package/src/components/chat/trace-grouping/tool-row.ts +74 -0
- package/src/components/chat/trace-grouping/use-deploy-work-disclosure.ts +35 -0
- package/src/components/chat/trace-grouping/use-work-chain.ts +103 -0
- package/src/components/chat/trace-i18n/core.ts +48 -0
- package/src/components/chat/trace-i18n/index.tsx +23 -0
- package/src/components/chat/trace-i18n/locales/en.ts +24 -0
- package/src/components/chat/trace-i18n/locales/fr.ts +28 -0
- package/src/components/chat/trace-i18n/types.ts +35 -0
- package/src/components/chat/trace-spacing.ts +249 -0
- package/src/components/chat/transcript/activity-view.ts +375 -0
- package/src/components/chat/transcript/chat-export.ts +133 -0
- package/src/components/chat/transcript/index.ts +10 -0
- package/src/components/chat/transcript/save-file.ts +209 -0
- package/src/components/chat/transcript/transcript-markdown.ts +428 -0
- package/src/components/chat/transcript/types.ts +81 -0
- package/src/components/chat/turn-outcome.ts +52 -0
- package/src/components/chat/use-warmup-phase.ts +18 -0
- package/src/components/chat/work-status-lead.tsx +49 -0
- package/src/components/chrome.tsx +264 -0
- package/src/components/command-palette/CommandPalette.tsx +404 -0
- package/src/components/command-palette/index.ts +6 -0
- package/src/components/contact/ContactMailto.tsx +112 -0
- package/src/components/contact/index.ts +5 -0
- package/src/components/conviction/ConvictionMeter.tsx +40 -0
- package/src/components/conviction/SignedConvictionBadge.tsx +26 -0
- package/src/components/conviction/index.ts +7 -0
- package/src/components/data-layout/BentoGrid.tsx +52 -0
- package/src/components/data-layout/CardRail.tsx +150 -0
- package/src/components/data-layout/DotMatrixStat.tsx +65 -0
- package/src/components/data-layout/FeatureCell.tsx +80 -0
- package/src/components/data-layout/MediaFrame.tsx +125 -0
- package/src/components/data-layout/ModuleGrid.tsx +735 -0
- package/src/components/data-layout/Odometer.tsx +149 -0
- package/src/components/data-layout/ProductFrame.tsx +79 -0
- package/src/components/data-layout/TestimonialWall.tsx +139 -0
- package/src/components/data-layout/index.ts +19 -0
- package/src/components/data-layout/treemap.ts +368 -0
- package/src/components/deck/DeckStack.tsx +243 -0
- package/src/components/desk/DataTable.tsx +373 -0
- package/src/components/desk/KpiGrid.tsx +80 -0
- package/src/components/desk/Sparkline.tsx +63 -0
- package/src/components/desk/StateBlock.tsx +132 -0
- package/src/components/desk/index.ts +18 -0
- package/src/components/diagrams/ArchitectureDiagram.tsx +350 -0
- package/src/components/diagrams/ArchitectureTour.tsx +912 -0
- package/src/components/diagrams/TerminalSchematic.tsx +192 -0
- package/src/components/diagrams/architecture-svg.tsx +384 -0
- package/src/components/diagrams/index.ts +30 -0
- package/src/components/diagrams/mermaid-theme.ts +115 -0
- package/src/components/diagrams/tour-camera.ts +171 -0
- package/src/components/docs/CodeTabs.tsx +90 -0
- package/src/components/docs/CopyCommand.tsx +151 -0
- package/src/components/docs/DocsLayout.tsx +197 -0
- package/src/components/docs/DocsSearch.tsx +97 -0
- package/src/components/docs/Endpoint.tsx +135 -0
- package/src/components/document/DocumentFrame.tsx +43 -0
- package/src/components/document/GlyphList.tsx +51 -0
- package/src/components/document/PageTitle.tsx +44 -0
- package/src/components/document/Prose.tsx +38 -0
- package/src/components/document/Section.tsx +55 -0
- package/src/components/document/index.ts +13 -0
- package/src/components/effects-chrome/AnnouncementBar.tsx +88 -0
- package/src/components/effects-chrome/HorizontalScrollTrack.tsx +461 -0
- package/src/components/effects-chrome/Pipeline.tsx +226 -0
- package/src/components/effects-chrome/RotatingPrompts.tsx +116 -0
- package/src/components/effects-chrome/StackingPanels.tsx +101 -0
- package/src/components/effects-chrome/TabStrip.tsx +232 -0
- package/src/components/effects-chrome/ToastStack.tsx +201 -0
- package/src/components/effects-chrome/horizontal-track-core.ts +70 -0
- package/src/components/effects-chrome/index.ts +35 -0
- package/src/components/emblems.tsx +188 -0
- package/src/components/faviconTheme.ts +23 -0
- package/src/components/figure/Figure.tsx +38 -0
- package/src/components/figure/index.ts +2 -0
- package/src/components/finance-charts/DrawdownPlot.tsx +76 -0
- package/src/components/finance-charts/EquityCurve.tsx +71 -0
- package/src/components/finance-charts/PriceChart.tsx +119 -0
- package/src/components/finance-charts/chart-host.tsx +236 -0
- package/src/components/finance-charts/chart-scale.ts +93 -0
- package/src/components/finance-charts/demo-data.ts +88 -0
- package/src/components/finance-charts/index.ts +46 -0
- package/src/components/finance-charts/lw-chart.tsx +292 -0
- package/src/components/finance-composites/MarketBar.tsx +243 -0
- package/src/components/finance-composites/OrderBook.tsx +133 -0
- package/src/components/finance-composites/PerformanceDashboard.tsx +185 -0
- package/src/components/finance-composites/SortableTable.tsx +144 -0
- package/src/components/finance-composites/StockTicker.tsx +65 -0
- package/src/components/finance-composites/SyncedTearsheet.tsx +226 -0
- package/src/components/finance-composites/index.ts +28 -0
- package/src/components/finance-tearsheet/KpiStrip.tsx +49 -0
- package/src/components/finance-tearsheet/LiveBadge.tsx +29 -0
- package/src/components/finance-tearsheet/ReturnsMatrix.tsx +321 -0
- package/src/components/finance-tearsheet/StrategyTearsheet.tsx +99 -0
- package/src/components/finance-tearsheet/TearsheetCard.tsx +54 -0
- package/src/components/finance-tearsheet/TradeLogTable.tsx +73 -0
- package/src/components/finance-tearsheet/charts.tsx +2624 -0
- package/src/components/finance-tearsheet/demo-data.ts +177 -0
- package/src/components/finance-tearsheet/format.ts +41 -0
- package/src/components/finance-tearsheet/index.ts +120 -0
- package/src/components/finance-tearsheet/print.ts +52 -0
- package/src/components/finance-tearsheet/risk-bands.ts +48 -0
- package/src/components/finance-tearsheet/stats.ts +35 -0
- package/src/components/finance-tearsheet/strategy-sheet-model.ts +238 -0
- package/src/components/finance-tearsheet/types.ts +58 -0
- package/src/components/footer/FooterCells.tsx +109 -0
- package/src/components/footer/index.ts +6 -0
- package/src/components/graph.tsx +158 -0
- package/src/components/icons.tsx +98 -0
- package/src/components/landing/PixelWordmark.tsx +36 -0
- package/src/components/logos.ts +39 -0
- package/src/components/marquee/Marquee.tsx +204 -0
- package/src/components/metrics/PerfMetrics.tsx +68 -0
- package/src/components/metrics/StatCounter.tsx +104 -0
- package/src/components/page-geometry.tsx +177 -0
- package/src/components/pricing/Pricing.tsx +174 -0
- package/src/components/pricing/PricingMatrix.tsx +219 -0
- package/src/components/prose/index.ts +2 -0
- package/src/components/prose/prose.tsx +86 -0
- package/src/components/repo-activity/RepoActivity.tsx +599 -0
- package/src/components/repo-activity/RepoHeatmap.tsx +225 -0
- package/src/components/repo-activity/demo.ts +201 -0
- package/src/components/repo-activity/fetch.ts +211 -0
- package/src/components/repo-activity/heatmap.ts +123 -0
- package/src/components/repo-activity/index.ts +28 -0
- package/src/components/repo-activity/types.ts +99 -0
- package/src/components/stages/NumberedStages.tsx +116 -0
- package/src/components/symbols/marks.tsx +116 -0
- package/src/components/symbols/terminal-lockup.tsx +136 -0
- package/src/components/symbols/terminal-marks.tsx +226 -0
- package/src/components/terminal/ChatResponseLoader.tsx +67 -0
- package/src/components/terminal/ContainerBootLoader.tsx +82 -0
- package/src/components/terminal/TerminalStepCaret.tsx +202 -0
- package/src/components/themeInit.ts +24 -0
- package/src/components/typography/MotionReveal.tsx +108 -0
- package/src/components/typography/TypewriterReveal.tsx +101 -0
- package/src/components/typography/WordReveal.tsx +143 -0
- package/src/data/modules.ts +378 -0
- package/src/data/subsystems.ts +101 -0
- package/src/index.ts +587 -0
- package/src/lib/base-path.ts +60 -0
- package/src/lib/cx.ts +25 -0
- package/src/lib/gloomberb.ts +69 -0
- package/src/lib/record.ts +19 -0
- package/src/lib/stub-payload.ts +183 -0
- package/src/lib/useBodyScrollLock.ts +44 -0
- package/src/lib/utils.ts +6 -0
- package/src/motion/primitives.tsx +105 -0
- package/src/motion/scroll-glide.ts +62 -0
- package/src/motion/scrolly-core.ts +38 -0
- package/src/motion/scrolly.tsx +95 -0
- package/src/navigation/HashScrollManager.tsx +167 -0
- package/src/navigation/hashScroll.ts +78 -0
- package/src/replay/browser.ts +280 -0
- package/src/replay/chat-export-run.ts +121 -0
- package/src/replay/export-format.ts +25 -0
- package/src/replay/export-mount.ts +32 -0
- package/src/replay/filename.ts +54 -0
- package/src/replay/index.ts +21 -0
- package/src/replay/redact-dom.ts +178 -0
- package/src/replay/redact.ts +113 -0
- package/src/replay/snapshot.ts +659 -0
- package/src/styles/account-auth.css +339 -0
- package/src/styles/brand-wordmark.css +11 -0
- package/src/styles/card-rail.css +120 -0
- package/src/styles/chat-aui.css +1143 -0
- package/src/styles/chat-core.css +469 -0
- package/src/styles/chat-digichat.css +2531 -0
- package/src/styles/chat-math.css +70 -0
- package/src/styles/chat-skin-palette.css +138 -0
- package/src/styles/chat-stock-menu.css +37 -0
- package/src/styles/chat-widgets.css +190 -0
- package/src/styles/command-palette.css +372 -0
- package/src/styles/controls-core.css +2261 -0
- package/src/styles/controls-overlay.css +603 -0
- package/src/styles/data-layout.css +288 -0
- package/src/styles/deck.css +141 -0
- package/src/styles/diagrams.css +1860 -0
- package/src/styles/digichat-app-theme.css +250 -0
- package/src/styles/digichat-boot-loader.css +487 -0
- package/src/styles/digichat-launcher.css +377 -0
- package/src/styles/docs.css +165 -0
- package/src/styles/effects-chrome.css +888 -0
- package/src/styles/finance-composites.css +214 -0
- package/src/styles/finance-tearsheet-overlay.css +90 -0
- package/src/styles/finance-tearsheet.css +945 -0
- package/src/styles/marquee.css +168 -0
- package/src/styles/metrics.css +82 -0
- package/src/styles/nav-shell.css +551 -0
- package/src/styles/pricing.css +79 -0
- package/src/styles/repo-activity.css +575 -0
- package/src/styles/stages.css +69 -0
- package/src/styles/terminal-loaders.css +122 -0
- package/src/styles/terminal-manifest.css +228 -0
- package/src/styles/web-theme.css +1007 -0
- package/src/styles/word-reveal.css +204 -0
- package/src/theme/apply-theme.ts +214 -0
- package/src/theme/index.ts +27 -0
- package/src/theme/palette-registry.ts +855 -0
- package/src/theme/tui-prefs.ts +64 -0
- package/src/ui/alert.tsx +78 -0
- package/src/ui/avatar.tsx +104 -0
- package/src/ui/badge.tsx +75 -0
- package/src/ui/breadcrumb.tsx +61 -0
- package/src/ui/button.tsx +67 -0
- package/src/ui/card.tsx +139 -0
- package/src/ui/checkbox.tsx +27 -0
- package/src/ui/collapsible.tsx +32 -0
- package/src/ui/date-pager.tsx +325 -0
- package/src/ui/dialog.tsx +157 -0
- package/src/ui/dropdown-menu.tsx +267 -0
- package/src/ui/empty-state.tsx +165 -0
- package/src/ui/field.tsx +104 -0
- package/src/ui/form.tsx +36 -0
- package/src/ui/icon-button.tsx +36 -0
- package/src/ui/index.ts +34 -0
- package/src/ui/input.tsx +33 -0
- package/src/ui/kbd.tsx +30 -0
- package/src/ui/label.tsx +32 -0
- package/src/ui/pager.tsx +119 -0
- package/src/ui/pagination.tsx +126 -0
- package/src/ui/radio-group.tsx +51 -0
- package/src/ui/search-bar.tsx +85 -0
- package/src/ui/segmented-control.tsx +76 -0
- package/src/ui/select.tsx +257 -0
- package/src/ui/separator.tsx +24 -0
- package/src/ui/sheet.tsx +141 -0
- package/src/ui/skeleton.tsx +76 -0
- package/src/ui/slider.tsx +76 -0
- package/src/ui/switch.tsx +31 -0
- package/src/ui/table.tsx +179 -0
- package/src/ui/tabs.tsx +89 -0
- package/src/ui/tags-input.tsx +168 -0
- package/src/ui/textarea.tsx +17 -0
- package/src/ui/tooltip.tsx +71 -0
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { Button } from "../../ui";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Pipeline — the dashboard's workflow visualization promoted from the
|
|
8
|
+
* design reference (effects/pipeline). Stages flow left→right, a `parallel`
|
|
9
|
+
* column fans out into a dashed group, each node carries its diagnostics
|
|
10
|
+
* (wall time · tokens · model), and selecting a node opens its inputs/outputs
|
|
11
|
+
* and full diagnostics in the detail panel below. Works for sequential and
|
|
12
|
+
* parallel graphs alike; an optional summary strip carries the run-level
|
|
13
|
+
* readout. All copy and numbers arrive via props — preformatted display
|
|
14
|
+
* strings, the component never formats.
|
|
15
|
+
*
|
|
16
|
+
* Selection is uncontrolled (`defaultSelectedId` + `onSelect`); status wears
|
|
17
|
+
* the accent for `running` and fades `queued`, per the reference grammar.
|
|
18
|
+
*
|
|
19
|
+
* Client component (selection state). Wiring (in the consuming app):
|
|
20
|
+
* globals.css @import "@digithings-ai/ui/styles/effects-chrome.css";
|
|
21
|
+
* @source "<path-to>/packages/ui/src/components/effects-chrome";
|
|
22
|
+
*/
|
|
23
|
+
export type PipelineStatus = "done" | "running" | "queued";
|
|
24
|
+
|
|
25
|
+
export type PipelineNode = {
|
|
26
|
+
id: string;
|
|
27
|
+
/** Mono node name — "ingest", "backtest" … */
|
|
28
|
+
label: string;
|
|
29
|
+
status: PipelineStatus;
|
|
30
|
+
/**
|
|
31
|
+
* One line saying what this stage does, rendered under the name in the detail
|
|
32
|
+
* panel. Optional, and the only field here that is not a run diagnostic: a
|
|
33
|
+
* pipeline drawn as a *definition* rather than as a completed run has nothing
|
|
34
|
+
* to report for wall time or tokens but still has to explain itself.
|
|
35
|
+
*/
|
|
36
|
+
note?: string;
|
|
37
|
+
/** Preformatted wall-time read — "2.41s", "120ms", "—". */
|
|
38
|
+
ms?: string;
|
|
39
|
+
/** Preformatted token read — "8.2k", "—". */
|
|
40
|
+
tokens?: string;
|
|
41
|
+
/** Model / engine chip — "opus", "nautilus", "polars" … */
|
|
42
|
+
model?: string;
|
|
43
|
+
/** What fed in — one preformatted line. Absent for a stage drawn as a
|
|
44
|
+
* definition, where nothing was measured and no artefact was handed over. */
|
|
45
|
+
inputs?: string;
|
|
46
|
+
/** What came out — one preformatted line. */
|
|
47
|
+
outputs?: string;
|
|
48
|
+
/** Preformatted cost read — "$0.11", "—". */
|
|
49
|
+
cost?: string;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export type PipelineColumn = {
|
|
53
|
+
id: string;
|
|
54
|
+
/** `parallel` draws the dashed fan-out group around the column's nodes. */
|
|
55
|
+
kind: "step" | "parallel";
|
|
56
|
+
/** Tag over a parallel group — "parallel · 3". */
|
|
57
|
+
label?: string;
|
|
58
|
+
nodes: PipelineNode[];
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export type PipelineSummaryItem = {
|
|
62
|
+
/** Mono micro-caps key — "wall time", "cost" … */
|
|
63
|
+
label: string;
|
|
64
|
+
/** Preformatted value — "8.4s", "$0.13". */
|
|
65
|
+
value: string;
|
|
66
|
+
/** Accent the value (the live "running" read). */
|
|
67
|
+
running?: boolean;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export type PipelineProps = {
|
|
71
|
+
columns: PipelineColumn[];
|
|
72
|
+
/** Optional run-level readout strip above the flow. */
|
|
73
|
+
summary?: PipelineSummaryItem[];
|
|
74
|
+
/** Node opened on first render — defaults to the first node. */
|
|
75
|
+
defaultSelectedId?: string;
|
|
76
|
+
onSelect?: (node: PipelineNode) => void;
|
|
77
|
+
className?: string;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export function Pipeline({ columns, summary, defaultSelectedId, onSelect, className }: PipelineProps) {
|
|
81
|
+
const [selId, setSelId] = useState(defaultSelectedId);
|
|
82
|
+
const all = columns.flatMap((c) => c.nodes);
|
|
83
|
+
const sel = all.find((n) => n.id === selId) ?? all[0];
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<div className={className}>
|
|
87
|
+
{summary && summary.length > 0 ? (
|
|
88
|
+
<div
|
|
89
|
+
className="pl-summary flex flex-wrap gap-x-[2rem] gap-y-0 rounded-none border border-hair bg-surface/55 px-[1.1rem] py-[0.85rem]"
|
|
90
|
+
role="list"
|
|
91
|
+
>
|
|
92
|
+
{summary.map((s) => (
|
|
93
|
+
<span key={s.label} role="listitem">
|
|
94
|
+
<span className="pl-sum-k">{s.label}</span>
|
|
95
|
+
<span className={`pl-sum-v${s.running ? " pl-running" : ""}`}>{s.value}</span>
|
|
96
|
+
</span>
|
|
97
|
+
))}
|
|
98
|
+
</div>
|
|
99
|
+
) : null}
|
|
100
|
+
|
|
101
|
+
<div
|
|
102
|
+
className={`pl-flow flex items-center gap-0 overflow-x-auto pt-[0.4rem] px-[0.1rem] pb-[0.8rem]${
|
|
103
|
+
summary && summary.length > 0 ? " mt-[1rem]" : ""
|
|
104
|
+
}`}
|
|
105
|
+
>
|
|
106
|
+
{columns.map((col, i) => (
|
|
107
|
+
<div key={col.id} className="pl-colwrap flex items-center shrink-0">
|
|
108
|
+
<div className={`pl-col flex flex-col gap-[0.5rem]${col.kind === "parallel" ? " pl-col--par" : ""}`}>
|
|
109
|
+
{col.label ? <span className="pl-col-tag">{col.label}</span> : null}
|
|
110
|
+
{col.nodes.map((n) => (
|
|
111
|
+
<Button
|
|
112
|
+
key={n.id}
|
|
113
|
+
type="button"
|
|
114
|
+
variant="ghost"
|
|
115
|
+
className={`pl-node h-auto items-stretch justify-start border-hair bg-surface px-[0.7rem] py-[0.6rem] font-normal whitespace-normal hover:bg-transparent pl-node--${n.status}${sel && n.id === sel.id ? " is-sel" : ""}`}
|
|
116
|
+
aria-pressed={sel ? n.id === sel.id : false}
|
|
117
|
+
onClick={() => {
|
|
118
|
+
setSelId(n.id);
|
|
119
|
+
onSelect?.(n);
|
|
120
|
+
}}
|
|
121
|
+
>
|
|
122
|
+
<span className="pl-node-head">
|
|
123
|
+
<span className="pl-pip" aria-hidden="true" />
|
|
124
|
+
<span className="pl-node-label">{n.label}</span>
|
|
125
|
+
{/* The pip dot (aria-hidden) and the pl-node--${status}
|
|
126
|
+
class are the only place a node's done/running/queued
|
|
127
|
+
state renders — a screen reader gets aria-pressed
|
|
128
|
+
(selection) but nothing for run status unless this
|
|
129
|
+
node happens to be the one currently open in the
|
|
130
|
+
detail panel below. .pl-sr, not Tailwind's sr-only:
|
|
131
|
+
this is a shared component (see the .tl-sr/.tm-sr
|
|
132
|
+
precedent in terminal-loaders.css/terminal-manifest.css
|
|
133
|
+
for why). */}
|
|
134
|
+
<span className="pl-sr">{n.status}</span>
|
|
135
|
+
</span>
|
|
136
|
+
{n.ms || n.tokens ? (
|
|
137
|
+
<span className="pl-node-diag">
|
|
138
|
+
{n.ms ? <span>{n.ms}</span> : null}
|
|
139
|
+
{n.tokens ? <span className="pl-node-tok">{n.tokens} tok</span> : null}
|
|
140
|
+
</span>
|
|
141
|
+
) : null}
|
|
142
|
+
</Button>
|
|
143
|
+
))}
|
|
144
|
+
</div>
|
|
145
|
+
{i < columns.length - 1 ? (
|
|
146
|
+
<span className="pl-conn" aria-hidden="true">
|
|
147
|
+
<svg
|
|
148
|
+
viewBox="0 0 24 24"
|
|
149
|
+
width="18"
|
|
150
|
+
height="18"
|
|
151
|
+
fill="none"
|
|
152
|
+
stroke="currentColor"
|
|
153
|
+
strokeWidth="1.6"
|
|
154
|
+
strokeLinecap="round"
|
|
155
|
+
strokeLinejoin="round"
|
|
156
|
+
>
|
|
157
|
+
<path d="M4 12h15M13 6l6 6-6 6" />
|
|
158
|
+
</svg>
|
|
159
|
+
</span>
|
|
160
|
+
) : null}
|
|
161
|
+
</div>
|
|
162
|
+
))}
|
|
163
|
+
</div>
|
|
164
|
+
|
|
165
|
+
{sel ? (
|
|
166
|
+
<div className="pl-detail">
|
|
167
|
+
<div className="pl-detail-head">
|
|
168
|
+
<span className={`pl-pip pl-pip--${sel.status}`} aria-hidden="true" />
|
|
169
|
+
<span className="pl-detail-name">{sel.label}</span>
|
|
170
|
+
{sel.model ? <span className="pl-detail-model">{sel.model}</span> : null}
|
|
171
|
+
<span className={`pl-detail-status pl-detail-status--${sel.status}`}>{sel.status}</span>
|
|
172
|
+
</div>
|
|
173
|
+
{sel.note ? <p className="pl-note">{sel.note}</p> : null}
|
|
174
|
+
{sel.inputs || sel.outputs ? (
|
|
175
|
+
<dl className="pl-io">
|
|
176
|
+
{sel.inputs ? (
|
|
177
|
+
<div>
|
|
178
|
+
<dt>inputs</dt>
|
|
179
|
+
<dd>{sel.inputs}</dd>
|
|
180
|
+
</div>
|
|
181
|
+
) : null}
|
|
182
|
+
{sel.outputs ? (
|
|
183
|
+
<div>
|
|
184
|
+
<dt>outputs</dt>
|
|
185
|
+
<dd>{sel.outputs}</dd>
|
|
186
|
+
</div>
|
|
187
|
+
) : null}
|
|
188
|
+
</dl>
|
|
189
|
+
) : null}
|
|
190
|
+
{/* Only the diagnostics this node actually has. A pipeline drawn as a
|
|
191
|
+
definition has no wall time or token count to report, and printing
|
|
192
|
+
an em dash row for each of them would read as a measurement that
|
|
193
|
+
came back empty rather than one that was never taken. */}
|
|
194
|
+
{[sel.ms, sel.tokens, sel.model, sel.cost].some(Boolean) ? (
|
|
195
|
+
<div className="pl-diag-grid">
|
|
196
|
+
{sel.ms ? (
|
|
197
|
+
<div>
|
|
198
|
+
<span className="pl-diag-k">wall time</span>
|
|
199
|
+
<span className="pl-diag-v">{sel.ms}</span>
|
|
200
|
+
</div>
|
|
201
|
+
) : null}
|
|
202
|
+
{sel.tokens ? (
|
|
203
|
+
<div>
|
|
204
|
+
<span className="pl-diag-k">tokens</span>
|
|
205
|
+
<span className="pl-diag-v">{sel.tokens}</span>
|
|
206
|
+
</div>
|
|
207
|
+
) : null}
|
|
208
|
+
{sel.model ? (
|
|
209
|
+
<div>
|
|
210
|
+
<span className="pl-diag-k">model</span>
|
|
211
|
+
<span className="pl-diag-v">{sel.model}</span>
|
|
212
|
+
</div>
|
|
213
|
+
) : null}
|
|
214
|
+
{sel.cost ? (
|
|
215
|
+
<div>
|
|
216
|
+
<span className="pl-diag-k">cost</span>
|
|
217
|
+
<span className="pl-diag-v">{sel.cost}</span>
|
|
218
|
+
</div>
|
|
219
|
+
) : null}
|
|
220
|
+
</div>
|
|
221
|
+
) : null}
|
|
222
|
+
</div>
|
|
223
|
+
) : null}
|
|
224
|
+
</div>
|
|
225
|
+
);
|
|
226
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
4
|
+
import { m, useReducedMotion } from "motion/react";
|
|
5
|
+
import { Button } from "../../ui";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* RotatingPrompts — the rotating hero teaser line promoted from the design
|
|
9
|
+
* reference (effects/rotating-prompts): a mono `>` prompt shell whose text
|
|
10
|
+
* cycles through real developer questions with a rise-in transition and a
|
|
11
|
+
* blinking caret. The teaser is a door, not a poster — consumers pass the
|
|
12
|
+
* prompts and typically link the shell into the live product. Link the
|
|
13
|
+
* shell AS A SIBLING (wrap both in a common container and stretch the
|
|
14
|
+
* anchor over it, e.g. an absolutely-positioned full-cover `<a>`), not by
|
|
15
|
+
* nesting this component INSIDE an `<a>` — the shell renders a real
|
|
16
|
+
* `<button>` (the pause control below), and nesting interactive content
|
|
17
|
+
* inside other interactive content is invalid HTML: some browsers also
|
|
18
|
+
* resolve the ambiguous click target inconsistently, and the button's own
|
|
19
|
+
* preventDefault() cannot reliably cancel a `<button>`-inside-`<a>` parse
|
|
20
|
+
* in every engine the way it can for a normal, non-nested ancestor click
|
|
21
|
+
* listener.
|
|
22
|
+
*
|
|
23
|
+
* aria-live-safe: the shell announces politely; under reduced motion the
|
|
24
|
+
* rotation stops and the FIRST prompt renders statically (also the no-JS /
|
|
25
|
+
* server-rendered state, so the line always reads without JS). The caret's
|
|
26
|
+
* @keyframes blink lives in styles/effects-chrome.css.
|
|
27
|
+
*
|
|
28
|
+
* WCAG 2.2.2 (Pause, Stop, Hide): the rotation runs longer than 5s and starts
|
|
29
|
+
* automatically, so a visible pause control is required beyond the OS-level
|
|
30
|
+
* reduced-motion escape hatch — a click on the shell (or Enter/Space, since
|
|
31
|
+
* it is a real button) toggles it, aria-pressed carries the state.
|
|
32
|
+
*
|
|
33
|
+
* Client component (interval state). Wiring (in the consuming app):
|
|
34
|
+
* globals.css @import "@digithings-ai/ui/styles/effects-chrome.css";
|
|
35
|
+
* @source "<path-to>/packages/ui/src/components/effects-chrome";
|
|
36
|
+
*/
|
|
37
|
+
export type RotatingPromptsProps = {
|
|
38
|
+
/** The rotation, in order. First item is the reduced-motion / no-JS state. */
|
|
39
|
+
prompts: string[];
|
|
40
|
+
/** Dwell per prompt, ms. */
|
|
41
|
+
intervalMs?: number;
|
|
42
|
+
className?: string;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export function RotatingPrompts({ prompts, intervalMs = 3200, className }: RotatingPromptsProps) {
|
|
46
|
+
const reduced = useReducedMotion();
|
|
47
|
+
const [index, setIndex] = useState(0);
|
|
48
|
+
const [paused, setPaused] = useState(false);
|
|
49
|
+
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
if (reduced || paused || prompts.length < 2) return;
|
|
52
|
+
const timer = setInterval(() => setIndex((i) => (i + 1) % prompts.length), intervalMs);
|
|
53
|
+
return () => clearInterval(timer);
|
|
54
|
+
}, [reduced, paused, prompts.length, intervalMs]);
|
|
55
|
+
|
|
56
|
+
if (prompts.length === 0) return null;
|
|
57
|
+
const current = prompts[reduced ? 0 : index % prompts.length];
|
|
58
|
+
const showPauseControl = !reduced && prompts.length > 1;
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<div
|
|
62
|
+
className={`flex items-baseline gap-[0.55rem] rounded-none border border-hair bg-surface px-[1.2rem] py-[1rem] font-mono text-[0.9rem] min-h-[3.4rem]${
|
|
63
|
+
className ? ` ${className}` : ""
|
|
64
|
+
}`}
|
|
65
|
+
aria-live="polite"
|
|
66
|
+
>
|
|
67
|
+
<span className="text-accent" aria-hidden="true">
|
|
68
|
+
>
|
|
69
|
+
</span>
|
|
70
|
+
<m.span
|
|
71
|
+
key={reduced ? "static" : index}
|
|
72
|
+
className="text-ink"
|
|
73
|
+
initial={reduced ? false : { opacity: 0, y: 6 }}
|
|
74
|
+
animate={{ opacity: 1, y: 0 }}
|
|
75
|
+
transition={{ duration: 0.32, ease: [0.22, 1, 0.36, 1] }}
|
|
76
|
+
>
|
|
77
|
+
{current}
|
|
78
|
+
</m.span>
|
|
79
|
+
<span className="prompt-caret" aria-hidden="true" />
|
|
80
|
+
{showPauseControl ? (
|
|
81
|
+
<Button
|
|
82
|
+
type="button"
|
|
83
|
+
variant="ghost"
|
|
84
|
+
className="prompt-pause h-auto w-auto border-0 bg-transparent p-[0.2rem] hover:bg-transparent ms-auto"
|
|
85
|
+
// Belt-and-suspenders even though the header comment now tells
|
|
86
|
+
// consumers to link the shell as a SIBLING, not by nesting it
|
|
87
|
+
// inside an <a>: stopPropagation alone does NOT stop an ancestor
|
|
88
|
+
// <a>'s native navigation — only preventDefault does (the
|
|
89
|
+
// anchor's default action is checked once against the event's
|
|
90
|
+
// defaultPrevented flag after the full bubble phase, regardless
|
|
91
|
+
// of where propagation was stopped) — so both are kept: preventDefault
|
|
92
|
+
// so a click meant to pause never also navigates away, stopPropagation
|
|
93
|
+
// so it never also fires some other ancestor's own click handler.
|
|
94
|
+
onClick={(e) => {
|
|
95
|
+
e.preventDefault();
|
|
96
|
+
e.stopPropagation();
|
|
97
|
+
setPaused((p) => !p);
|
|
98
|
+
}}
|
|
99
|
+
aria-pressed={paused}
|
|
100
|
+
aria-label={paused ? "Resume rotating prompts" : "Pause rotating prompts"}
|
|
101
|
+
>
|
|
102
|
+
{paused ? (
|
|
103
|
+
<svg viewBox="0 0 24 24" width="13" height="13" fill="currentColor" aria-hidden="true">
|
|
104
|
+
<path d="M6 4l14 8-14 8z" />
|
|
105
|
+
</svg>
|
|
106
|
+
) : (
|
|
107
|
+
<svg viewBox="0 0 24 24" width="13" height="13" fill="currentColor" aria-hidden="true">
|
|
108
|
+
<rect x="5" y="4" width="5" height="16" />
|
|
109
|
+
<rect x="14" y="4" width="5" height="16" />
|
|
110
|
+
</svg>
|
|
111
|
+
)}
|
|
112
|
+
</Button>
|
|
113
|
+
) : null}
|
|
114
|
+
</div>
|
|
115
|
+
);
|
|
116
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useRef, type ReactNode } from "react";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* StackingPanels — the layered between-sections transition promoted from the
|
|
7
|
+
* design reference (effects/stacking-panels; mined from revolut.com). Each
|
|
8
|
+
* panel pins in turn and the next slides up over it (hairline + shadow
|
|
9
|
+
* give the layered seam); as a panel is covered it scales back and dims.
|
|
10
|
+
*
|
|
11
|
+
* The "covered" amount keys off the *next* panel's approach to the pin line —
|
|
12
|
+
* not the panel's own view progress — so it's driven by a manual scroll
|
|
13
|
+
* handler writing to refs (the ResearchPipeline / zoom-morph pattern), which
|
|
14
|
+
* also keeps it verifiable. Scroll mechanics stay here per migrate-vs-leave;
|
|
15
|
+
* only the panel CONTENT arrives via props. Reduced motion: a plain sticky
|
|
16
|
+
* stack, no transform/dim writes. Content is server-rendered and fully
|
|
17
|
+
* readable with no JS.
|
|
18
|
+
*
|
|
19
|
+
* The sticky pin offset reads `--nav-h` from the consuming app (fallback
|
|
20
|
+
* 3.4rem) — see styles/effects-chrome.css.
|
|
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 StackingPanel = {
|
|
27
|
+
/** Mono accent index over the title — "01", "2.0" … */
|
|
28
|
+
tag: string;
|
|
29
|
+
/** Serif panel title. */
|
|
30
|
+
title: string;
|
|
31
|
+
/** One or two sentences of mechanism. */
|
|
32
|
+
body: ReactNode;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export type StackingPanelsProps = {
|
|
36
|
+
panels: StackingPanel[];
|
|
37
|
+
className?: string;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const clamp = (v: number, a: number, b: number) => Math.max(a, Math.min(b, v));
|
|
41
|
+
|
|
42
|
+
export function StackingPanels({ panels, className }: StackingPanelsProps) {
|
|
43
|
+
const panelRefs = useRef<(HTMLElement | null)[]>([]);
|
|
44
|
+
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
const els = panelRefs.current.filter(Boolean) as HTMLElement[];
|
|
47
|
+
if (els.length < 2) return;
|
|
48
|
+
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
|
|
49
|
+
|
|
50
|
+
const stickyTop = parseFloat(getComputedStyle(els[0]).top) || 70;
|
|
51
|
+
|
|
52
|
+
let ticking = false;
|
|
53
|
+
const onScroll = () => {
|
|
54
|
+
if (ticking) return;
|
|
55
|
+
ticking = true;
|
|
56
|
+
requestAnimationFrame(() => {
|
|
57
|
+
const coverStart = window.innerHeight * 0.55;
|
|
58
|
+
for (let i = 0; i < els.length - 1; i++) {
|
|
59
|
+
const nextTop = els[i + 1].getBoundingClientRect().top;
|
|
60
|
+
const covered = clamp((stickyTop + coverStart - nextTop) / coverStart, 0, 1);
|
|
61
|
+
els[i].style.transform = `translateY(${covered * -14}px) scale(${1 - covered * 0.06})`;
|
|
62
|
+
const dim = els[i].querySelector<HTMLElement>(".stk-dim");
|
|
63
|
+
if (dim) dim.style.opacity = `${covered * 0.55}`;
|
|
64
|
+
}
|
|
65
|
+
ticking = false;
|
|
66
|
+
});
|
|
67
|
+
};
|
|
68
|
+
onScroll();
|
|
69
|
+
window.addEventListener("scroll", onScroll, { passive: true });
|
|
70
|
+
window.addEventListener("resize", onScroll, { passive: true });
|
|
71
|
+
return () => {
|
|
72
|
+
window.removeEventListener("scroll", onScroll);
|
|
73
|
+
window.removeEventListener("resize", onScroll);
|
|
74
|
+
};
|
|
75
|
+
}, [panels.length]);
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<div className={className}>
|
|
79
|
+
{panels.map((p, i) => (
|
|
80
|
+
<section
|
|
81
|
+
key={p.tag}
|
|
82
|
+
className="stk-panel"
|
|
83
|
+
ref={(el) => {
|
|
84
|
+
panelRefs.current[i] = el;
|
|
85
|
+
}}
|
|
86
|
+
>
|
|
87
|
+
<div className="relative z-[1] max-w-[34rem]">
|
|
88
|
+
<span className="font-mono text-[0.7rem] tracking-[0.14em] text-accent">{p.tag}</span>
|
|
89
|
+
<h3 className="mt-[0.4rem] font-display font-normal text-[clamp(1.8rem,4vw,2.8rem)] tracking-[-0.015em] text-ink">
|
|
90
|
+
{p.title}
|
|
91
|
+
</h3>
|
|
92
|
+
<p className="mt-[0.7rem] max-w-[42ch] text-[0.95rem] leading-[1.6] text-ink-soft">
|
|
93
|
+
{p.body}
|
|
94
|
+
</p>
|
|
95
|
+
</div>
|
|
96
|
+
<span className="stk-dim" aria-hidden="true" />
|
|
97
|
+
</section>
|
|
98
|
+
))}
|
|
99
|
+
</div>
|
|
100
|
+
);
|
|
101
|
+
}
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useCallback, useLayoutEffect, useRef, type ReactNode } from "react";
|
|
4
|
+
import { useReducedMotion } from "motion/react";
|
|
5
|
+
import { Button } from "../../ui";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* TabStrip — the sliding-indicator tab strip promoted from the design
|
|
9
|
+
* reference (chrome/tabs). A proper tablist: role="tablist"/"tab",
|
|
10
|
+
* aria-selected, roving tabindex, ArrowLeft/Right + Home/End keyboard nav.
|
|
11
|
+
* The active indicator is a single absolutely-positioned element whose
|
|
12
|
+
* transform/width (and, for the box dresses, height) are measured from the
|
|
13
|
+
* active tab and written straight to a ref, so the slide is a CSS transition
|
|
14
|
+
* — no `layoutId` (the apps' LazyMotion runs `domAnimation`, which omits
|
|
15
|
+
* layout animations) and no per-frame React state. Survives resize and
|
|
16
|
+
* re-measures on a `dir` flip (ResizeObserver on the strip and its tabs;
|
|
17
|
+
* MutationObserver on <html>'s `dir`/`class`/`data-theme`/`style`), so the
|
|
18
|
+
* ink is correct the instant the document mirrors — not only after the next
|
|
19
|
+
* tab click. Honours reduced motion (indicator jumps, no slide). The ink
|
|
20
|
+
* anchors on
|
|
21
|
+
* `inset-inline-start` and measures the active tab from that same inline-start
|
|
22
|
+
* edge, so it lands under the active tab in both LTR and RTL; arrow-key nav
|
|
23
|
+
* swaps ArrowLeft/ArrowRight under RTL to match the visual order.
|
|
24
|
+
*
|
|
25
|
+
* Three dresses: `underline` for content regions, `pill` for a compact
|
|
26
|
+
* filled-rect mode switch (API name kept; shape is radius 0), `chip` for a
|
|
27
|
+
* wrapping row of bordered chips (dashboard sub-nav — mono face,
|
|
28
|
+
* accent-tinted active chip). `pill` and `chip` position the ink with
|
|
29
|
+
* translate(x, y) + measured height, so `chip` rows may flex-wrap. The strip is controlled (`active` + `onChange`);
|
|
30
|
+
* panels are consumer-owned — wire them with the exported `tabId`/
|
|
31
|
+
* `tabPanelId` helpers:
|
|
32
|
+
*
|
|
33
|
+
* <TabStrip tabs={TABS} active={i} onChange={setI} label="Account view" />
|
|
34
|
+
* <div role="tabpanel" id={tabPanelId("Account view", tab.id)}
|
|
35
|
+
* aria-labelledby={tabId("Account view", tab.id)}>…</div>
|
|
36
|
+
*
|
|
37
|
+
* That per-tab-id default is only correct when the consumer genuinely mounts
|
|
38
|
+
* ONE role="tabpanel" per tab. Content-swapping consumers — one physical
|
|
39
|
+
* panel whose content changes with `active`, which is the common case —
|
|
40
|
+
* must pass `sharedPanel` instead: every tab's aria-controls then points at
|
|
41
|
+
* the CURRENTLY ACTIVE tab's panel id (matching the one real panel, which
|
|
42
|
+
* the consumer keys the same way: id={tabPanelId(label, tabs[active].id)}),
|
|
43
|
+
* rather than each tab dangling a reference to an id that only exists while
|
|
44
|
+
* IT is the active one. Found live in two shipped consumers (#2272) and,
|
|
45
|
+
* closer to home, in this file's own reference showcase before this fix —
|
|
46
|
+
* the tell is a single tabpanel below the strip whose `key` changes with
|
|
47
|
+
* `active` rather than one tabpanel per tab.
|
|
48
|
+
*
|
|
49
|
+
* When the consumer's panels carry no DOM ids at all (e.g. a wrapper
|
|
50
|
+
* adapting legacy children, or no panel semantics apply), pass
|
|
51
|
+
* `linkPanels={false}` to omit `aria-controls` entirely — the attribute is
|
|
52
|
+
* optional in the APG tabs pattern, and a dangling reference is worse than
|
|
53
|
+
* none.
|
|
54
|
+
*
|
|
55
|
+
* Wiring (in the consuming app):
|
|
56
|
+
* globals.css @import "@digithings-ai/ui/styles/effects-chrome.css";
|
|
57
|
+
* @source "<path-to>/packages/ui/src/components/effects-chrome";
|
|
58
|
+
*/
|
|
59
|
+
export type TabItem = { id: string; label: ReactNode };
|
|
60
|
+
|
|
61
|
+
export type TabStripProps = {
|
|
62
|
+
tabs: TabItem[];
|
|
63
|
+
/** Index of the active tab. */
|
|
64
|
+
active: number;
|
|
65
|
+
onChange: (index: number) => void;
|
|
66
|
+
/** Accessible tablist name; also seeds the tab/panel id pairing. */
|
|
67
|
+
label: string;
|
|
68
|
+
variant?: "underline" | "pill" | "chip";
|
|
69
|
+
/** Emit aria-controls → tabPanelId(label, id) (default). False = omit. */
|
|
70
|
+
linkPanels?: boolean;
|
|
71
|
+
/**
|
|
72
|
+
* True when the consumer mounts ONE physical role="tabpanel" whose content
|
|
73
|
+
* swaps with `active`, rather than one tabpanel per tab (see the module
|
|
74
|
+
* doc comment above). Every tab's aria-controls then points at the
|
|
75
|
+
* currently active tab's panel id instead of its own — the one id that
|
|
76
|
+
* actually exists in the DOM — so inactive tabs never dangle a reference.
|
|
77
|
+
* No-op when `linkPanels` is false.
|
|
78
|
+
*/
|
|
79
|
+
sharedPanel?: boolean;
|
|
80
|
+
className?: string;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
/** Id base derived from the tablist label (spaces collapsed to dashes). */
|
|
84
|
+
export function tabBaseId(label: string): string {
|
|
85
|
+
return label.replace(/\s+/g, "-");
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** DOM id of the tab button for `tabId(label, tab.id)` — pair with aria-labelledby. */
|
|
89
|
+
export function tabId(label: string, id: string): string {
|
|
90
|
+
return `${tabBaseId(label)}-tab-${id}`;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** DOM id the consumer's role="tabpanel" must carry (aria-controls points here). */
|
|
94
|
+
export function tabPanelId(label: string, id: string): string {
|
|
95
|
+
return `${tabBaseId(label)}-panel-${id}`;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function TabStrip({
|
|
99
|
+
tabs,
|
|
100
|
+
active,
|
|
101
|
+
onChange,
|
|
102
|
+
label,
|
|
103
|
+
variant = "underline",
|
|
104
|
+
linkPanels = true,
|
|
105
|
+
sharedPanel = false,
|
|
106
|
+
className,
|
|
107
|
+
}: TabStripProps) {
|
|
108
|
+
const listRef = useRef<HTMLDivElement>(null);
|
|
109
|
+
const inkRef = useRef<HTMLSpanElement>(null);
|
|
110
|
+
const mounted = useRef(false);
|
|
111
|
+
const reduced = useReducedMotion();
|
|
112
|
+
|
|
113
|
+
const position = useCallback(
|
|
114
|
+
(animate: boolean) => {
|
|
115
|
+
const list = listRef.current;
|
|
116
|
+
const ink = inkRef.current;
|
|
117
|
+
if (!list || !ink) return;
|
|
118
|
+
const el = list.querySelectorAll<HTMLButtonElement>('[role="tab"]')[active];
|
|
119
|
+
if (!el) return;
|
|
120
|
+
const listRect = list.getBoundingClientRect();
|
|
121
|
+
const elRect = el.getBoundingClientRect();
|
|
122
|
+
// The ink is anchored with inset-inline-start: 0, so its origin mirrors
|
|
123
|
+
// with the strip. Measure the active tab from that same inline-start
|
|
124
|
+
// edge (physical left in LTR, physical right in RTL) and translate by
|
|
125
|
+
// the physical delta — `transform` has no logical form, so RTL negates.
|
|
126
|
+
const rtl = getComputedStyle(list).direction === "rtl";
|
|
127
|
+
const x = rtl ? listRect.right - elRect.right : elRect.left - listRect.left;
|
|
128
|
+
const y = elRect.top - listRect.top;
|
|
129
|
+
ink.style.transition = animate ? "" : "none";
|
|
130
|
+
// translate(x, y): measured against the strip's padding box — the same
|
|
131
|
+
// origin as the ink's absolute inset-inline-start/top — so the box
|
|
132
|
+
// dresses (pill/chip) track the tab even across wrapped rows.
|
|
133
|
+
ink.style.transform = `translate(${rtl ? -x : x}px, ${y}px)`;
|
|
134
|
+
ink.style.width = `${elRect.width}px`;
|
|
135
|
+
// The underline ink keeps its CSS height (2px, strip-bottom anchored);
|
|
136
|
+
// the box dresses take the tab's own height.
|
|
137
|
+
if (variant !== "underline") ink.style.height = `${elRect.height}px`;
|
|
138
|
+
if (!animate) {
|
|
139
|
+
// flush the jump before restoring the transition so it never animates
|
|
140
|
+
void ink.offsetWidth;
|
|
141
|
+
ink.style.transition = "";
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
[active, variant],
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
useLayoutEffect(() => {
|
|
148
|
+
position(mounted.current && !reduced);
|
|
149
|
+
mounted.current = true;
|
|
150
|
+
}, [position, reduced]);
|
|
151
|
+
|
|
152
|
+
// Re-measure whenever the geometry the ink is derived from can change: the
|
|
153
|
+
// strip's own box (font/zoom/layout), any tab's box (label reflow, wrap),
|
|
154
|
+
// and the document's writing direction. A `dir` flip is the case the
|
|
155
|
+
// reference audit caught (/rtl): the ink kept its LTR offset ~372px from the
|
|
156
|
+
// active tab until the next click, because nothing re-ran `position()` — a
|
|
157
|
+
// window `resize` never fires for it. ResizeObserver covers the box changes;
|
|
158
|
+
// MutationObserver on <html>'s `dir` (plus `class`/`data-theme`/`style`,
|
|
159
|
+
// which can restyle the strip through a theme/livery swap) covers the
|
|
160
|
+
// direction flip and any out-of-React dir change, not just the one the kit's
|
|
161
|
+
// own DirectionProvider makes.
|
|
162
|
+
useLayoutEffect(() => {
|
|
163
|
+
const list = listRef.current;
|
|
164
|
+
if (!list) return;
|
|
165
|
+
const remeasure = () => position(false);
|
|
166
|
+
const ro = new ResizeObserver(remeasure);
|
|
167
|
+
ro.observe(list);
|
|
168
|
+
for (const tab of list.querySelectorAll<HTMLElement>('[role="tab"]')) ro.observe(tab);
|
|
169
|
+
const mo = new MutationObserver(remeasure);
|
|
170
|
+
mo.observe(document.documentElement, {
|
|
171
|
+
attributes: true,
|
|
172
|
+
attributeFilter: ["dir", "class", "data-theme", "style"],
|
|
173
|
+
});
|
|
174
|
+
window.addEventListener("resize", remeasure);
|
|
175
|
+
return () => {
|
|
176
|
+
ro.disconnect();
|
|
177
|
+
mo.disconnect();
|
|
178
|
+
window.removeEventListener("resize", remeasure);
|
|
179
|
+
};
|
|
180
|
+
}, [position]);
|
|
181
|
+
|
|
182
|
+
const onKeyDown = (e: React.KeyboardEvent) => {
|
|
183
|
+
// In an RTL strip the tabs run right-to-left, so "next" is visually to the
|
|
184
|
+
// left: ArrowLeft/ArrowRight swap their DOM-order meaning.
|
|
185
|
+
const rtl = listRef.current
|
|
186
|
+
? getComputedStyle(listRef.current).direction === "rtl"
|
|
187
|
+
: false;
|
|
188
|
+
const nextInDom = (active + 1) % tabs.length;
|
|
189
|
+
const prevInDom = (active - 1 + tabs.length) % tabs.length;
|
|
190
|
+
let next: number;
|
|
191
|
+
if (e.key === "ArrowRight") next = rtl ? prevInDom : nextInDom;
|
|
192
|
+
else if (e.key === "ArrowLeft") next = rtl ? nextInDom : prevInDom;
|
|
193
|
+
else if (e.key === "Home") next = 0;
|
|
194
|
+
else if (e.key === "End") next = tabs.length - 1;
|
|
195
|
+
else return;
|
|
196
|
+
e.preventDefault();
|
|
197
|
+
onChange(next);
|
|
198
|
+
listRef.current?.querySelectorAll<HTMLButtonElement>('[role="tab"]')[next]?.focus();
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
return (
|
|
202
|
+
<div
|
|
203
|
+
ref={listRef}
|
|
204
|
+
className={`tab-strip ${variant}${className ? ` ${className}` : ""}`}
|
|
205
|
+
role="tablist"
|
|
206
|
+
aria-label={label}
|
|
207
|
+
onKeyDown={onKeyDown}
|
|
208
|
+
>
|
|
209
|
+
<span ref={inkRef} className={`tab-ink ${variant}`} aria-hidden="true" />
|
|
210
|
+
{tabs.map((t, i) => (
|
|
211
|
+
<Button
|
|
212
|
+
key={t.id}
|
|
213
|
+
type="button"
|
|
214
|
+
role="tab"
|
|
215
|
+
variant="ghost"
|
|
216
|
+
id={tabId(label, t.id)}
|
|
217
|
+
aria-selected={i === active}
|
|
218
|
+
aria-controls={
|
|
219
|
+
linkPanels
|
|
220
|
+
? tabPanelId(label, sharedPanel ? tabs[active].id : t.id)
|
|
221
|
+
: undefined
|
|
222
|
+
}
|
|
223
|
+
tabIndex={i === active ? 0 : -1}
|
|
224
|
+
className="tab-btn h-auto inline-block border-0 bg-transparent text-[0.76rem] font-normal hover:bg-transparent"
|
|
225
|
+
onClick={() => onChange(i)}
|
|
226
|
+
>
|
|
227
|
+
{t.label}
|
|
228
|
+
</Button>
|
|
229
|
+
))}
|
|
230
|
+
</div>
|
|
231
|
+
);
|
|
232
|
+
}
|