@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,264 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
/** Shared nav, footer, and module card. Brand + links are passed in so both
|
|
3
|
+
* marketing apps reuse the same chrome. */
|
|
4
|
+
import { useRef, type AnchorHTMLAttributes, type ReactNode } from "react";
|
|
5
|
+
import { m, useScroll, useTransform } from "motion/react";
|
|
6
|
+
import type { VariantProps } from "class-variance-authority";
|
|
7
|
+
import { Emblem } from "./emblems";
|
|
8
|
+
import { StackRow } from "./StackLogo";
|
|
9
|
+
import { type ModuleNode } from "../data/modules";
|
|
10
|
+
import { useMotionSafe } from "../motion/primitives";
|
|
11
|
+
import { buttonVariants } from "../ui/button";
|
|
12
|
+
import { cn } from "../lib/utils";
|
|
13
|
+
|
|
14
|
+
export interface NavLink { label: string; href: string; external?: boolean; cta?: boolean; }
|
|
15
|
+
|
|
16
|
+
/** A labelled set of destinations — a dropdown menu on <NavShell/>'s wide bar,
|
|
17
|
+
* a labelled section of links in its narrow sheet. A group is a *sibling* type
|
|
18
|
+
* rather than an optional `items?` on NavLink because a group trigger has no
|
|
19
|
+
* destination of its own: making `href` optional to model that would widen it
|
|
20
|
+
* to `string | undefined` for every existing consumer and reader (the
|
|
21
|
+
* `key={l.href + l.label}` in this file included). A separate interface keeps
|
|
22
|
+
* `href` required where it belongs and makes "link or group" a real
|
|
23
|
+
* discriminated choice. */
|
|
24
|
+
export interface NavGroup { label: string; items: NavLink[]; }
|
|
25
|
+
|
|
26
|
+
/** One top-bar entry. `NavLink[]` stays assignable to `NavItem[]`, so every
|
|
27
|
+
* consumer that passes a flat link array today keeps compiling untouched. */
|
|
28
|
+
export type NavItem = NavLink | NavGroup;
|
|
29
|
+
|
|
30
|
+
/** Discriminates the NavItem union on the one field only a group carries. */
|
|
31
|
+
export function isNavGroup(item: NavItem): item is NavGroup {
|
|
32
|
+
return "items" in item;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** The kit's vocabulary for a CTA's dress, taken straight from buttonVariants
|
|
36
|
+
* so there is exactly one button treatment in the canon — a CTA link is the
|
|
37
|
+
* same dress as the Button it pairs with, never a parallel class family. */
|
|
38
|
+
type ButtonVariant = NonNullable<VariantProps<typeof buttonVariants>["variant"]>;
|
|
39
|
+
type ButtonSize = NonNullable<VariantProps<typeof buttonVariants>["size"]>;
|
|
40
|
+
|
|
41
|
+
/** Absolute or protocol-relative href — never a same-page route. */
|
|
42
|
+
const isExternalHref = (href: string) => /^[a-z][a-z0-9+.-]*:/i.test(href) || href.startsWith("//");
|
|
43
|
+
|
|
44
|
+
/** Whether `href` names the page the visitor is currently on. Owned here so
|
|
45
|
+
* every site marks `aria-current` by the same rule: an exact pathname match
|
|
46
|
+
* only — never an external link, and never a same-page anchor (`/#pipeline`,
|
|
47
|
+
* `#section`) since several of those share one pathname and would all light
|
|
48
|
+
* up. Trailing slashes are equivalent (`/docs` ≡ `/docs/`, the static-export
|
|
49
|
+
* shape). */
|
|
50
|
+
export function hrefIsCurrent(href: string, currentPath?: string): boolean {
|
|
51
|
+
if (!currentPath || isExternalHref(href) || href.includes("#")) return false;
|
|
52
|
+
const path = href.split("?")[0] ?? href;
|
|
53
|
+
const norm = (p: string): string => {
|
|
54
|
+
if (p.length <= 1) return p;
|
|
55
|
+
let end = p.length;
|
|
56
|
+
while (end > 0 && p.charCodeAt(end - 1) === 47) end -= 1;
|
|
57
|
+
return p.slice(0, end);
|
|
58
|
+
};
|
|
59
|
+
return norm(path) === norm(currentPath);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Skip-to-content — visually hidden until keyboard focus, then pinned to the
|
|
63
|
+
* top-left. Token utilities (the pre-canon app copy is what this replaces);
|
|
64
|
+
* the consumer owns the target's `id` and focusability. */
|
|
65
|
+
export function SkipLink({
|
|
66
|
+
href = "#main",
|
|
67
|
+
children = "Skip to content",
|
|
68
|
+
className,
|
|
69
|
+
}: {
|
|
70
|
+
href?: string;
|
|
71
|
+
children?: ReactNode;
|
|
72
|
+
className?: string;
|
|
73
|
+
}) {
|
|
74
|
+
return (
|
|
75
|
+
<a
|
|
76
|
+
href={href}
|
|
77
|
+
className={cn(
|
|
78
|
+
"fixed top-2 left-2 z-[300] -translate-y-[200%] rounded-none bg-[var(--accent)] px-[0.9rem] py-2 font-mono text-[0.72rem] text-[var(--on-accent)] no-underline focus-visible:translate-y-0",
|
|
79
|
+
className,
|
|
80
|
+
)}
|
|
81
|
+
>
|
|
82
|
+
{children}
|
|
83
|
+
</a>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface CtaLinkProps extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>, "href"> {
|
|
88
|
+
href: string;
|
|
89
|
+
/** buttonVariants variant — the single CTA dress vocabulary. */
|
|
90
|
+
variant?: ButtonVariant;
|
|
91
|
+
size?: ButtonSize;
|
|
92
|
+
/** External destination: opens a new tab with the matching rel. */
|
|
93
|
+
external?: boolean;
|
|
94
|
+
/** Optional leading glyph, rendered before the label. */
|
|
95
|
+
icon?: ReactNode;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** A CTA link dressed with the kit's button treatment. The kit shipped a
|
|
99
|
+
* `<Button>` (a real button) but no link equivalent, so every site hand-rolled
|
|
100
|
+
* `buttonVariants(...)` at the call site (or invented a `.dc-nav-cta` /
|
|
101
|
+
* `.dq-cta` class). This is that one treatment, named. */
|
|
102
|
+
export function CtaLink({
|
|
103
|
+
href,
|
|
104
|
+
children,
|
|
105
|
+
variant = "default",
|
|
106
|
+
size = "sm",
|
|
107
|
+
external,
|
|
108
|
+
icon,
|
|
109
|
+
className,
|
|
110
|
+
...rest
|
|
111
|
+
}: CtaLinkProps) {
|
|
112
|
+
return (
|
|
113
|
+
<a
|
|
114
|
+
href={href}
|
|
115
|
+
className={cn(buttonVariants({ variant, size }), className)}
|
|
116
|
+
target={external ? "_blank" : undefined}
|
|
117
|
+
rel={external ? "noopener noreferrer" : undefined}
|
|
118
|
+
{...rest}
|
|
119
|
+
>
|
|
120
|
+
{icon}
|
|
121
|
+
{children}
|
|
122
|
+
</a>
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export interface IconLinkProps
|
|
127
|
+
extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>, "href" | "aria-label"> {
|
|
128
|
+
href: string;
|
|
129
|
+
/** The accessible name — required, because the child is a bare glyph. */
|
|
130
|
+
label: string;
|
|
131
|
+
external?: boolean;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Icon-only link (the nav tail's GitHub glyph, a mark-only CTA). The kit's
|
|
135
|
+
* ghost icon-button dress, with the `aria-label` the bare svg needs made
|
|
136
|
+
* mandatory rather than left to each call site. */
|
|
137
|
+
export function IconLink({ href, label, external, className, children, ...rest }: IconLinkProps) {
|
|
138
|
+
return (
|
|
139
|
+
<a
|
|
140
|
+
href={href}
|
|
141
|
+
aria-label={label}
|
|
142
|
+
className={cn(buttonVariants({ variant: "ghost", size: "icon-sm" }), className)}
|
|
143
|
+
target={external ? "_blank" : undefined}
|
|
144
|
+
rel={external ? "noopener noreferrer" : undefined}
|
|
145
|
+
{...rest}
|
|
146
|
+
>
|
|
147
|
+
{children}
|
|
148
|
+
</a>
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function Footer({
|
|
153
|
+
links,
|
|
154
|
+
meta,
|
|
155
|
+
profiles,
|
|
156
|
+
}: {
|
|
157
|
+
links: NavLink[];
|
|
158
|
+
meta: string;
|
|
159
|
+
/** Quiet company-profile row (typically <SocialRow/>). Optional so existing
|
|
160
|
+
* call sites stay a utility-link strip; socials are a dedicated primitive,
|
|
161
|
+
* not a fake Connect column. */
|
|
162
|
+
profiles?: ReactNode;
|
|
163
|
+
}) {
|
|
164
|
+
return (
|
|
165
|
+
<footer className="footer">
|
|
166
|
+
<div className="wrap footer-inner">
|
|
167
|
+
<nav className="footer-links" aria-label="Footer">
|
|
168
|
+
{links.map((l) => (
|
|
169
|
+
<a key={l.href + l.label} href={l.href} target={l.external ? "_blank" : undefined}
|
|
170
|
+
rel={l.external ? "noopener noreferrer" : undefined}>{l.label}</a>
|
|
171
|
+
))}
|
|
172
|
+
</nav>
|
|
173
|
+
{profiles}
|
|
174
|
+
<p className="footer-meta">{meta}</p>
|
|
175
|
+
</div>
|
|
176
|
+
</footer>
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** The page's last word (canon §08): the module's name at giant scale,
|
|
181
|
+
* 1px hairline outline by default — plus an opt-in scroll-scrubbed glow
|
|
182
|
+
* sweep. The outline rise/fill is scroll-scrubbed with zero JS via CSS
|
|
183
|
+
* `animation-timeline: view()` (@supports-gated in site.css); under
|
|
184
|
+
* reduced motion or without support the name simply stands. The suffix
|
|
185
|
+
* wears var(--accent), so each app's livery (or the umbrella's ink)
|
|
186
|
+
* dresses it automatically. aria-hidden: punctuation, not content.
|
|
187
|
+
*
|
|
188
|
+
* `sweep` (default false — the outline-only ruling stands for every
|
|
189
|
+
* existing consumer) adds the reference footer's personality moment: an
|
|
190
|
+
* accent glow passing left→right across the wordmark, scrubbed by the
|
|
191
|
+
* colophon's own scroll progress (same offsets/transform as
|
|
192
|
+
* reference/components/footer-reference.tsx). A duplicate overlay span
|
|
193
|
+
* carries the gradient clipped to its glyphs; reduced motion (and no-JS)
|
|
194
|
+
* park the band off-screen so no glow travels. Requires a MotionProvider
|
|
195
|
+
* in the consuming app.
|
|
196
|
+
*
|
|
197
|
+
* The rise/fill only works because `.colophon` clips with `overflow-x: clip`
|
|
198
|
+
* rather than `overflow: hidden` (site.css): `hidden` made the colophon its
|
|
199
|
+
* own child's nearest scrollport, so `.colo-word`'s `view()` timeline
|
|
200
|
+
* resolved against a box that never scrolls, saturated, and the whole
|
|
201
|
+
* scroll-scrub silently no-opped (the mark sat fully risen at every scroll
|
|
202
|
+
* position). Do not "simplify" that back to `overflow: hidden`. */
|
|
203
|
+
export function Colophon({
|
|
204
|
+
name,
|
|
205
|
+
suffix,
|
|
206
|
+
sweep = false,
|
|
207
|
+
}: {
|
|
208
|
+
name: string;
|
|
209
|
+
suffix?: string;
|
|
210
|
+
sweep?: boolean;
|
|
211
|
+
}) {
|
|
212
|
+
const ref = useRef<HTMLDivElement | null>(null);
|
|
213
|
+
// useMotionSafe(), not raw useReducedMotion() -- see WordReveal.tsx's fix
|
|
214
|
+
// comment (#2244) for the full hydration-mismatch mechanism this avoids.
|
|
215
|
+
const reduced = !useMotionSafe();
|
|
216
|
+
// 0 = the colophon's top enters the viewport bottom; 1 = scrolled to its
|
|
217
|
+
// end. The band travels off-left → off-right across the middle of that
|
|
218
|
+
// range, so the highlight crosses the wordmark once as you scroll into it.
|
|
219
|
+
const { scrollYProgress } = useScroll({
|
|
220
|
+
target: ref,
|
|
221
|
+
offset: ["start end", "end end"],
|
|
222
|
+
});
|
|
223
|
+
const sweepPos = useTransform(scrollYProgress, [0.38, 0.96], [120, -20]);
|
|
224
|
+
const backgroundPosition = useTransform(sweepPos, (v) => `${v}% 0`);
|
|
225
|
+
|
|
226
|
+
return (
|
|
227
|
+
<div className="colophon" aria-hidden="true" ref={ref}>
|
|
228
|
+
<span className="colo-word">
|
|
229
|
+
{name}
|
|
230
|
+
{suffix ? <b>{suffix}</b> : null}
|
|
231
|
+
{sweep ? (
|
|
232
|
+
<m.span
|
|
233
|
+
className="colo-sweep"
|
|
234
|
+
style={reduced ? undefined : { backgroundPosition }}
|
|
235
|
+
>
|
|
236
|
+
{name}
|
|
237
|
+
{suffix}
|
|
238
|
+
</m.span>
|
|
239
|
+
) : null}
|
|
240
|
+
</span>
|
|
241
|
+
</div>
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export function ModuleCard({
|
|
246
|
+
m,
|
|
247
|
+
hrefForModule = (id: string) => `/modules/${id}`,
|
|
248
|
+
}: {
|
|
249
|
+
m: ModuleNode;
|
|
250
|
+
/** Host-owned module URL. Defaults to the `/modules/[id]` host contract. */
|
|
251
|
+
hrefForModule?: (id: string) => string;
|
|
252
|
+
}) {
|
|
253
|
+
return (
|
|
254
|
+
<a className={`mod-card t-${m.tier}`} href={hrefForModule(m.id)}>
|
|
255
|
+
<div className="mod-card-top">
|
|
256
|
+
<Emblem id={m.emblem} size={26} />
|
|
257
|
+
<span className={`dg-tier t-${m.tier}`}>{m.tier}</span>
|
|
258
|
+
</div>
|
|
259
|
+
<h3>{m.name}</h3>
|
|
260
|
+
<p className="role">{m.role}</p>
|
|
261
|
+
<StackRow items={m.stack.slice(0, 4)} className="stack-row compact" />
|
|
262
|
+
</a>
|
|
263
|
+
);
|
|
264
|
+
}
|
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
useEffect,
|
|
5
|
+
useId,
|
|
6
|
+
useLayoutEffect,
|
|
7
|
+
useRef,
|
|
8
|
+
useState,
|
|
9
|
+
type ReactNode,
|
|
10
|
+
} from "react";
|
|
11
|
+
import { createPortal } from "react-dom";
|
|
12
|
+
import { useBodyScrollLock } from "../../lib/useBodyScrollLock";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* CommandPalette — the ⌘K command bar shell promoted from the design
|
|
16
|
+
* reference (chrome/command-palette). The shell owns the chrome and the
|
|
17
|
+
* interaction contract: overlay/backdrop, a document.body portal, the search
|
|
18
|
+
* input row, the grouped listbox, the keyboard loop (↑/↓/Home/End/↵/esc,
|
|
19
|
+
* focus held on the input) and the listbox ARIA wiring — `role="listbox"`
|
|
20
|
+
* with `role="option"` children and `aria-activedescendant` on the listbox
|
|
21
|
+
* (the pattern the dashboard's palette was fixed to; preserved here).
|
|
22
|
+
*
|
|
23
|
+
* The CONSUMER owns everything data- and router-shaped: the `open` flag (and
|
|
24
|
+
* whatever global shortcut toggles it — the shell binds no ⌘K), the grouped
|
|
25
|
+
* item list — recomputed per keystroke when `groups` is a function of the
|
|
26
|
+
* query — and per-item `onSelect` callbacks (router.push, theme toggles, …).
|
|
27
|
+
* No router, data fetching, or filtering policy lives here.
|
|
28
|
+
*
|
|
29
|
+
* Query state is internal and resets on every open, synchronously on the
|
|
30
|
+
* opening render, so a reopen never flashes the previous query. Enter or
|
|
31
|
+
* click selects the active item: palette-level `onSelect(item)` fires, then
|
|
32
|
+
* `item.onSelect()`, then `onClose()` unless `closeOnSelect={false}`.
|
|
33
|
+
*
|
|
34
|
+
* Wiring (in the consuming app's globals.css):
|
|
35
|
+
* @import "@digithings-ai/ui/styles/command-palette.css";
|
|
36
|
+
* @source "<path-to>/packages/ui/src/components/command-palette";
|
|
37
|
+
* Overlay stacking: the overlay's z-index reads var(--cp-z, 40) — set --cp-z
|
|
38
|
+
* at the call site when the app's chrome stacks higher than the reference's.
|
|
39
|
+
*
|
|
40
|
+
* Two dresses (styles/command-palette.css):
|
|
41
|
+
* - dress="reference" (default): the design-reference dress — mono rows,
|
|
42
|
+
* surface-mix panel, token scrim.
|
|
43
|
+
* - dress="glass": the dashboard's shipped ⌘K look, translated
|
|
44
|
+
* exactly — black/75 blurred scrim, term-bg panel (32rem, 12px radius),
|
|
45
|
+
* sans rows with accent-ring active state.
|
|
46
|
+
*/
|
|
47
|
+
export type CommandPaletteItem = {
|
|
48
|
+
/** Stable identity — React key; option DOM ids are index-derived. */
|
|
49
|
+
id: string;
|
|
50
|
+
label: ReactNode;
|
|
51
|
+
/** Optional muted second line under the label (the dashboard "hint"). */
|
|
52
|
+
description?: ReactNode;
|
|
53
|
+
/** Fired when the row is chosen (Enter or click) — router.push lives here. */
|
|
54
|
+
onSelect?: () => void;
|
|
55
|
+
/** Optional right-edge keycap hint — "B", "⌘K". */
|
|
56
|
+
kbd?: string;
|
|
57
|
+
/** Livery scope class for the row (`accent-<livery>`) — colors the dot. */
|
|
58
|
+
livery?: string;
|
|
59
|
+
/** Show the leading livery dot (reference grammar). */
|
|
60
|
+
dot?: boolean;
|
|
61
|
+
/** Leading icon node — wins over `dot` when both are set. */
|
|
62
|
+
icon?: ReactNode;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export type CommandPaletteGroup = {
|
|
66
|
+
id?: string;
|
|
67
|
+
/** Optional group heading; unlabeled groups render without `role="group"`. */
|
|
68
|
+
label?: ReactNode;
|
|
69
|
+
items: CommandPaletteItem[];
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export type CommandPaletteProps = {
|
|
73
|
+
/** Controlled visibility — the consumer owns the flag and its shortcut. */
|
|
74
|
+
open: boolean;
|
|
75
|
+
/** Fired by esc, the scrim, and (by default) after a selection. */
|
|
76
|
+
onClose: () => void;
|
|
77
|
+
/**
|
|
78
|
+
* The grouped item list. Pass a function to recompute it per keystroke —
|
|
79
|
+
* it receives the shell's current query verbatim (filtering policy is
|
|
80
|
+
* entirely the consumer's).
|
|
81
|
+
*/
|
|
82
|
+
groups: CommandPaletteGroup[] | ((query: string) => CommandPaletteGroup[]);
|
|
83
|
+
/** Palette-level selection hook, fired before the item's own onSelect. */
|
|
84
|
+
onSelect?: (item: CommandPaletteItem) => void;
|
|
85
|
+
/** Close the palette after a selection (default true). */
|
|
86
|
+
closeOnSelect?: boolean;
|
|
87
|
+
placeholder?: string;
|
|
88
|
+
/**
|
|
89
|
+
* Footer key-hint legend: `true` renders the standard ↑↓ / ↵ / esc legend,
|
|
90
|
+
* a node renders custom content inside `.cp-foot`, absent renders nothing.
|
|
91
|
+
*/
|
|
92
|
+
footer?: ReactNode | boolean;
|
|
93
|
+
/** Trailing input-row slot — defaults to an `esc` keycap. */
|
|
94
|
+
inputTrailing?: ReactNode;
|
|
95
|
+
/** Leading input-row slot — defaults to the built-in magnifier glyph. */
|
|
96
|
+
inputLeading?: ReactNode;
|
|
97
|
+
/**
|
|
98
|
+
* Input `type` attribute (default "text"). dashboard ships type="search"
|
|
99
|
+
* (native search-cancel affordance in WebKit/Chromium).
|
|
100
|
+
*/
|
|
101
|
+
inputType?: string;
|
|
102
|
+
/** Empty-results content; the function form receives the current query. */
|
|
103
|
+
emptyMessage?: ReactNode | ((query: string) => ReactNode);
|
|
104
|
+
/** Dialog aria-label (default "Command palette"). */
|
|
105
|
+
ariaLabel?: string;
|
|
106
|
+
/** Listbox aria-label (default "Commands"). */
|
|
107
|
+
listLabel?: string;
|
|
108
|
+
/** Input aria-label (default "Search commands"). */
|
|
109
|
+
inputAriaLabel?: string;
|
|
110
|
+
/** Extra class on the panel. */
|
|
111
|
+
className?: string;
|
|
112
|
+
/** Render into a document.body portal (default) or in place. */
|
|
113
|
+
portal?: boolean;
|
|
114
|
+
/** Visual dress — "reference" (default) or dashboard's "glass". */
|
|
115
|
+
dress?: "reference" | "glass";
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const defaultEmpty = (query: string): ReactNode =>
|
|
119
|
+
query ? <>No matches for “{query}”.</> : "No matches.";
|
|
120
|
+
|
|
121
|
+
// Focus-trap boundary query — deliberately generic, not "just the input":
|
|
122
|
+
// consumers can pass interactive inputLeading/inputTrailing content (dashboard
|
|
123
|
+
// ships a real Close button there), so the trap must cycle the dialog's
|
|
124
|
+
// actual focusable set rather than assume the input is the only one.
|
|
125
|
+
const FOCUSABLE_SELECTOR =
|
|
126
|
+
'a[href]:not([tabindex^="-"]), button:not([disabled]):not([tabindex^="-"]), input:not([disabled]):not([tabindex^="-"]), select:not([disabled]):not([tabindex^="-"]), textarea:not([disabled]):not([tabindex^="-"]), [tabindex]:not([tabindex^="-"])';
|
|
127
|
+
|
|
128
|
+
export function CommandPalette({
|
|
129
|
+
open,
|
|
130
|
+
onClose,
|
|
131
|
+
groups,
|
|
132
|
+
onSelect,
|
|
133
|
+
closeOnSelect = true,
|
|
134
|
+
placeholder = "Type a command or search…",
|
|
135
|
+
footer,
|
|
136
|
+
inputTrailing,
|
|
137
|
+
inputLeading,
|
|
138
|
+
inputType = "text",
|
|
139
|
+
emptyMessage,
|
|
140
|
+
ariaLabel = "Command palette",
|
|
141
|
+
listLabel = "Commands",
|
|
142
|
+
inputAriaLabel = "Search commands",
|
|
143
|
+
className,
|
|
144
|
+
portal = true,
|
|
145
|
+
dress = "reference",
|
|
146
|
+
}: CommandPaletteProps) {
|
|
147
|
+
const baseId = useId();
|
|
148
|
+
const [query, setQuery] = useState("");
|
|
149
|
+
const [active, setActive] = useState(0);
|
|
150
|
+
const [mounted, setMounted] = useState(false);
|
|
151
|
+
const listRef = useRef<HTMLDivElement | null>(null);
|
|
152
|
+
const panelRef = useRef<HTMLDivElement | null>(null);
|
|
153
|
+
|
|
154
|
+
// Reset synchronously on the opening render — a reopen never flashes the
|
|
155
|
+
// previous query (React's adjust-state-during-render pattern).
|
|
156
|
+
const [prevOpen, setPrevOpen] = useState(open);
|
|
157
|
+
if (open !== prevOpen) {
|
|
158
|
+
setPrevOpen(open);
|
|
159
|
+
if (open) {
|
|
160
|
+
setQuery("");
|
|
161
|
+
setActive(0);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
useEffect(() => setMounted(true), []);
|
|
166
|
+
|
|
167
|
+
// Lock body scroll while the dialog is open — reference-counted (see
|
|
168
|
+
// useBodyScrollLock) so this coordinates safely with any other overlay
|
|
169
|
+
// (e.g. NavShell's own menu) that may be locked at the same time,
|
|
170
|
+
// regardless of open/close order.
|
|
171
|
+
useBodyScrollLock(open);
|
|
172
|
+
|
|
173
|
+
const resolvedGroups = typeof groups === "function" ? groups(query) : groups;
|
|
174
|
+
const flat = resolvedGroups.flatMap((g) => g.items);
|
|
175
|
+
const count = flat.length;
|
|
176
|
+
const activeIndex = count === 0 ? -1 : Math.min(active, count - 1);
|
|
177
|
+
|
|
178
|
+
const select = (item: CommandPaletteItem) => {
|
|
179
|
+
onSelect?.(item);
|
|
180
|
+
item.onSelect?.();
|
|
181
|
+
if (closeOnSelect) onClose();
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
// Refs keep one keydown listener per open, not one per keystroke.
|
|
185
|
+
const flatRef = useRef(flat);
|
|
186
|
+
const activeRef = useRef(activeIndex);
|
|
187
|
+
const selectRef = useRef(select);
|
|
188
|
+
const onCloseRef = useRef(onClose);
|
|
189
|
+
useLayoutEffect(() => {
|
|
190
|
+
flatRef.current = flat;
|
|
191
|
+
activeRef.current = activeIndex;
|
|
192
|
+
selectRef.current = select;
|
|
193
|
+
onCloseRef.current = onClose;
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
useEffect(() => {
|
|
197
|
+
if (!open) return;
|
|
198
|
+
const onKey = (e: KeyboardEvent) => {
|
|
199
|
+
if (e.key === "Escape") {
|
|
200
|
+
e.preventDefault();
|
|
201
|
+
onCloseRef.current();
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
if (e.key === "Tab") {
|
|
205
|
+
// Focus trap: only intercept at the dialog's boundary — wrap
|
|
206
|
+
// Tab past the last focusable back to the first, and Shift+Tab
|
|
207
|
+
// before the first back to the last — rather than pinning focus to
|
|
208
|
+
// the input unconditionally, since a consumer's inputLeading/
|
|
209
|
+
// inputTrailing slot can carry its own focusable content (dashboard's
|
|
210
|
+
// production palette ships a real Close button there).
|
|
211
|
+
const panel = panelRef.current;
|
|
212
|
+
if (!panel) return;
|
|
213
|
+
const focusables = Array.from(panel.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR));
|
|
214
|
+
if (focusables.length === 0) return;
|
|
215
|
+
const first = focusables[0];
|
|
216
|
+
const last = focusables[focusables.length - 1];
|
|
217
|
+
const activeEl = document.activeElement;
|
|
218
|
+
if (e.shiftKey) {
|
|
219
|
+
if (activeEl === first || !panel.contains(activeEl)) {
|
|
220
|
+
e.preventDefault();
|
|
221
|
+
last.focus();
|
|
222
|
+
}
|
|
223
|
+
} else {
|
|
224
|
+
if (activeEl === last || !panel.contains(activeEl)) {
|
|
225
|
+
e.preventDefault();
|
|
226
|
+
first.focus();
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
const len = flatRef.current.length;
|
|
232
|
+
if (len === 0) return;
|
|
233
|
+
if (e.key === "ArrowDown") {
|
|
234
|
+
e.preventDefault();
|
|
235
|
+
setActive((i) => Math.min(i + 1, len - 1));
|
|
236
|
+
} else if (e.key === "ArrowUp") {
|
|
237
|
+
e.preventDefault();
|
|
238
|
+
setActive((i) => Math.max(i - 1, 0));
|
|
239
|
+
} else if (e.key === "Home") {
|
|
240
|
+
e.preventDefault();
|
|
241
|
+
setActive(0);
|
|
242
|
+
} else if (e.key === "End") {
|
|
243
|
+
e.preventDefault();
|
|
244
|
+
setActive(len - 1);
|
|
245
|
+
} else if (e.key === "Enter") {
|
|
246
|
+
e.preventDefault();
|
|
247
|
+
const idx = Math.min(Math.max(0, activeRef.current), len - 1);
|
|
248
|
+
const item = flatRef.current[idx];
|
|
249
|
+
if (item) selectRef.current(item);
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
window.addEventListener("keydown", onKey);
|
|
253
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
254
|
+
}, [open]);
|
|
255
|
+
|
|
256
|
+
// Keep the active row in view as the keyboard walks the list.
|
|
257
|
+
useLayoutEffect(() => {
|
|
258
|
+
if (!open || activeIndex < 0 || !listRef.current) return;
|
|
259
|
+
listRef.current
|
|
260
|
+
.querySelector(`[data-cp-index="${activeIndex}"]`)
|
|
261
|
+
?.scrollIntoView({ block: "nearest" });
|
|
262
|
+
}, [open, activeIndex, query]);
|
|
263
|
+
|
|
264
|
+
if (!open) return null;
|
|
265
|
+
|
|
266
|
+
const empty =
|
|
267
|
+
typeof emptyMessage === "function"
|
|
268
|
+
? emptyMessage(query)
|
|
269
|
+
: emptyMessage ?? defaultEmpty(query);
|
|
270
|
+
|
|
271
|
+
let flatIndex = -1;
|
|
272
|
+
const palette = (
|
|
273
|
+
<div
|
|
274
|
+
className={`cp-overlay${dress === "glass" ? " cp-overlay--glass" : ""}`}
|
|
275
|
+
role="dialog"
|
|
276
|
+
aria-modal="true"
|
|
277
|
+
aria-label={ariaLabel}
|
|
278
|
+
>
|
|
279
|
+
<div className="cp-scrim" onClick={onClose} aria-hidden="true" />
|
|
280
|
+
<div ref={panelRef} className={`cp-panel${className ? ` ${className}` : ""}`}>
|
|
281
|
+
<div className="cp-input-row">
|
|
282
|
+
{inputLeading !== undefined ? (
|
|
283
|
+
inputLeading
|
|
284
|
+
) : (
|
|
285
|
+
<span className="cp-search-glyph" aria-hidden="true">
|
|
286
|
+
<svg
|
|
287
|
+
viewBox="0 0 24 24"
|
|
288
|
+
width="16"
|
|
289
|
+
height="16"
|
|
290
|
+
fill="none"
|
|
291
|
+
stroke="currentColor"
|
|
292
|
+
strokeWidth="1.6"
|
|
293
|
+
>
|
|
294
|
+
<circle cx="11" cy="11" r="7" />
|
|
295
|
+
<path d="M20 20l-3.5-3.5" strokeLinecap="round" />
|
|
296
|
+
</svg>
|
|
297
|
+
</span>
|
|
298
|
+
)}
|
|
299
|
+
<input
|
|
300
|
+
className="cp-input"
|
|
301
|
+
type={inputType}
|
|
302
|
+
placeholder={placeholder}
|
|
303
|
+
value={query}
|
|
304
|
+
autoFocus
|
|
305
|
+
autoComplete="off"
|
|
306
|
+
spellCheck={false}
|
|
307
|
+
aria-label={inputAriaLabel}
|
|
308
|
+
onChange={(e) => {
|
|
309
|
+
setQuery(e.target.value);
|
|
310
|
+
setActive(0);
|
|
311
|
+
}}
|
|
312
|
+
/>
|
|
313
|
+
{inputTrailing !== undefined ? inputTrailing : <kbd className="cp-kbd">esc</kbd>}
|
|
314
|
+
</div>
|
|
315
|
+
|
|
316
|
+
<div
|
|
317
|
+
ref={listRef}
|
|
318
|
+
className="cp-results"
|
|
319
|
+
role="listbox"
|
|
320
|
+
aria-label={listLabel}
|
|
321
|
+
aria-activedescendant={activeIndex >= 0 ? `${baseId}-opt-${activeIndex}` : undefined}
|
|
322
|
+
>
|
|
323
|
+
{count === 0 ? (
|
|
324
|
+
<p className="cp-empty" role="presentation">
|
|
325
|
+
{empty}
|
|
326
|
+
</p>
|
|
327
|
+
) : (
|
|
328
|
+
resolvedGroups.map((g, gi) => {
|
|
329
|
+
if (g.items.length === 0) return null;
|
|
330
|
+
const labelId = g.label != null ? `${baseId}-grp-${gi}` : undefined;
|
|
331
|
+
const rows = g.items.map((item) => {
|
|
332
|
+
flatIndex += 1;
|
|
333
|
+
const idx = flatIndex;
|
|
334
|
+
const on = idx === activeIndex;
|
|
335
|
+
return (
|
|
336
|
+
<div
|
|
337
|
+
key={item.id}
|
|
338
|
+
id={`${baseId}-opt-${idx}`}
|
|
339
|
+
role="option"
|
|
340
|
+
aria-selected={on}
|
|
341
|
+
data-cp-index={idx}
|
|
342
|
+
className={`cp-row${on ? " on" : ""}${item.livery ? ` accent-${item.livery}` : ""}`}
|
|
343
|
+
onClick={() => select(item)}
|
|
344
|
+
onMouseEnter={() => setActive(idx)}
|
|
345
|
+
>
|
|
346
|
+
{item.icon ? (
|
|
347
|
+
<span className="cp-icon" aria-hidden="true">
|
|
348
|
+
{item.icon}
|
|
349
|
+
</span>
|
|
350
|
+
) : item.dot ? (
|
|
351
|
+
<span className="cp-dot" aria-hidden="true" />
|
|
352
|
+
) : null}
|
|
353
|
+
<span className="cp-label">
|
|
354
|
+
{item.label}
|
|
355
|
+
{item.description != null ? (
|
|
356
|
+
<span className="cp-desc">{item.description}</span>
|
|
357
|
+
) : null}
|
|
358
|
+
</span>
|
|
359
|
+
{item.kbd ? <kbd className="cp-kbd cp-row-key">{item.kbd}</kbd> : null}
|
|
360
|
+
</div>
|
|
361
|
+
);
|
|
362
|
+
});
|
|
363
|
+
return g.label != null ? (
|
|
364
|
+
<div key={g.id ?? gi} className="cp-group" role="group" aria-labelledby={labelId}>
|
|
365
|
+
<p className="cp-group-label" id={labelId} role="presentation">
|
|
366
|
+
{g.label}
|
|
367
|
+
</p>
|
|
368
|
+
{rows}
|
|
369
|
+
</div>
|
|
370
|
+
) : (
|
|
371
|
+
<div key={g.id ?? gi} className="cp-group" role="presentation">
|
|
372
|
+
{rows}
|
|
373
|
+
</div>
|
|
374
|
+
);
|
|
375
|
+
})
|
|
376
|
+
)}
|
|
377
|
+
</div>
|
|
378
|
+
|
|
379
|
+
{footer === true ? (
|
|
380
|
+
<div className="cp-foot" aria-hidden="true">
|
|
381
|
+
<span>
|
|
382
|
+
<kbd className="cp-kbd">↑</kbd>
|
|
383
|
+
<kbd className="cp-kbd">↓</kbd> navigate
|
|
384
|
+
</span>
|
|
385
|
+
<span>
|
|
386
|
+
<kbd className="cp-kbd">↵</kbd> open
|
|
387
|
+
</span>
|
|
388
|
+
<span>
|
|
389
|
+
<kbd className="cp-kbd">esc</kbd> close
|
|
390
|
+
</span>
|
|
391
|
+
</div>
|
|
392
|
+
) : footer ? (
|
|
393
|
+
<div className="cp-foot" aria-hidden="true">
|
|
394
|
+
{footer}
|
|
395
|
+
</div>
|
|
396
|
+
) : null}
|
|
397
|
+
</div>
|
|
398
|
+
</div>
|
|
399
|
+
);
|
|
400
|
+
|
|
401
|
+
if (!portal) return palette;
|
|
402
|
+
if (!mounted) return null;
|
|
403
|
+
return createPortal(palette, document.body);
|
|
404
|
+
}
|