@ciderpress/ui 1.0.0-rc.1
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/LICENSE +21 -0
- package/README.md +23 -0
- package/dist/0~142.js +2 -0
- package/dist/0~164.js +2 -0
- package/dist/0~2.js +2 -0
- package/dist/0~225.js +2 -0
- package/dist/0~288.js +2 -0
- package/dist/0~299.js +2 -0
- package/dist/0~3.js +2 -0
- package/dist/0~301.js +2 -0
- package/dist/0~311.js +2 -0
- package/dist/0~326.js +2 -0
- package/dist/0~40.js +2 -0
- package/dist/0~43.js +2 -0
- package/dist/0~433.js +2 -0
- package/dist/0~453.js +2 -0
- package/dist/0~457.js +2 -0
- package/dist/0~497.js +2 -0
- package/dist/0~511.js +2 -0
- package/dist/0~603.js +2 -0
- package/dist/0~616.js +2 -0
- package/dist/0~659.js +2 -0
- package/dist/0~670.js +2 -0
- package/dist/0~687.js +2 -0
- package/dist/0~706.js +2 -0
- package/dist/0~729.js +2 -0
- package/dist/0~768.js +2 -0
- package/dist/0~819.js +2 -0
- package/dist/0~830.js +2 -0
- package/dist/0~854.js +2 -0
- package/dist/0~863.js +2 -0
- package/dist/0~879.js +2 -0
- package/dist/0~96.js +2 -0
- package/dist/0~97.js +2 -0
- package/dist/components/FileTree/FileTree.css +90 -0
- package/dist/components/FileTree/FileTree.d.ts +7 -0
- package/dist/components/FileTree/FileTree.js +531 -0
- package/dist/components/FileTree/FileTreeItem.d.ts +8 -0
- package/dist/components/FileTree/RemoteSvgIcon.d.ts +11 -0
- package/dist/components/FileTree/index.d.ts +4 -0
- package/dist/components/folder-icons.d.ts +32 -0
- package/dist/components/languages.d.ts +7 -0
- package/dist/components/tree-parser/tree-parser.d.ts +12 -0
- package/dist/components/tree-parser/types.d.ts +11 -0
- package/dist/head/css/loader-apple.css +1 -0
- package/dist/head/css/loader-backdrop.css +1 -0
- package/dist/head/css/loader-dots.css +1 -0
- package/dist/head/css/themes/amber.css +1 -0
- package/dist/head/css/themes/arcade.css +1 -0
- package/dist/head/css/themes/grannysmith.css +1 -0
- package/dist/head/css/themes/honeycrisp.css +1 -0
- package/dist/head/css/themes/midnight.css +1 -0
- package/dist/head/css/themes/mulled.css +1 -0
- package/dist/head/js/loader-dots.js +1 -0
- package/dist/head/js/vscode-nav.js +1 -0
- package/dist/index.d.ts +43 -0
- package/dist/index.mjs +40 -0
- package/dist/node.d.ts +81 -0
- package/dist/node.mjs +17762 -0
- package/dist/plugins/mermaid/MermaidRenderer.tsx +480 -0
- package/dist/plugins/mermaid/mermaid.css +199 -0
- package/dist/theme/components/announcement/announcement-bar.css +103 -0
- package/dist/theme/components/announcement/announcement-bar.tsx +173 -0
- package/dist/theme/components/ask-ai/ask-ai-button.css +52 -0
- package/dist/theme/components/ask-ai/ask-ai-button.tsx +56 -0
- package/dist/theme/components/content-footer/content-footer-portal.tsx +70 -0
- package/dist/theme/components/content-footer/feedback.css +47 -0
- package/dist/theme/components/content-footer/feedback.tsx +96 -0
- package/dist/theme/components/content-footer/meta-actions.css +25 -0
- package/dist/theme/components/content-footer/meta-actions.tsx +57 -0
- package/dist/theme/components/content-footer/page-pager.css +61 -0
- package/dist/theme/components/content-footer/page-pager.tsx +110 -0
- package/dist/theme/components/edit-source-button.tsx +97 -0
- package/dist/theme/components/footer/site-footer.css +179 -0
- package/dist/theme/components/footer/site-footer.tsx +115 -0
- package/dist/theme/components/home/cta.css +67 -0
- package/dist/theme/components/home/cta.tsx +77 -0
- package/dist/theme/components/home/feature-card.css +52 -0
- package/dist/theme/components/home/feature-card.tsx +131 -0
- package/dist/theme/components/home/feature.tsx +79 -0
- package/dist/theme/components/home/hero-demo.css +66 -0
- package/dist/theme/components/home/hero-demo.tsx +46 -0
- package/dist/theme/components/home/hero.css +96 -0
- package/dist/theme/components/home/hero.tsx +111 -0
- package/dist/theme/components/home/layout.tsx +183 -0
- package/dist/theme/components/home/page-rail.css +21 -0
- package/dist/theme/components/home/page-rail.tsx +19 -0
- package/dist/theme/components/home/split.css +148 -0
- package/dist/theme/components/home/split.tsx +120 -0
- package/dist/theme/components/home/trust-strip.css +33 -0
- package/dist/theme/components/home/trust-strip.tsx +43 -0
- package/dist/theme/components/home/workspaces.tsx +71 -0
- package/dist/theme/components/nav/branch-tag.css +54 -0
- package/dist/theme/components/nav/branch-tag.tsx +101 -0
- package/dist/theme/components/nav/ciderpress-doc-layout.tsx +247 -0
- package/dist/theme/components/nav/ciderpress-docs-bar.css +505 -0
- package/dist/theme/components/nav/ciderpress-docs-bar.tsx +426 -0
- package/dist/theme/components/nav/ciderpress-header.css +172 -0
- package/dist/theme/components/nav/ciderpress-header.tsx +113 -0
- package/dist/theme/components/nav/ciderpress-layout-context.tsx +49 -0
- package/dist/theme/components/nav/ciderpress-nav-hamburger.css +193 -0
- package/dist/theme/components/nav/ciderpress-nav-hamburger.tsx +112 -0
- package/dist/theme/components/nav/ciderpress-nav-menu.css +130 -0
- package/dist/theme/components/nav/ciderpress-nav-menu.tsx +372 -0
- package/dist/theme/components/nav/ciderpress-nav-search.css +73 -0
- package/dist/theme/components/nav/ciderpress-nav-search.tsx +55 -0
- package/dist/theme/components/nav/ciderpress-nav-social-links.css +35 -0
- package/dist/theme/components/nav/ciderpress-nav-social-links.tsx +87 -0
- package/dist/theme/components/nav/floating-branch-indicator.css +106 -0
- package/dist/theme/components/nav/floating-branch-indicator.tsx +109 -0
- package/dist/theme/components/nav/layout.tsx +268 -0
- package/dist/theme/components/nav/mobile-nav-cta.tsx +79 -0
- package/dist/theme/components/nav/nav-divider.css +15 -0
- package/dist/theme/components/nav/nav-divider.tsx +19 -0
- package/dist/theme/components/nav/nav-logo.css +42 -0
- package/dist/theme/components/nav/nav-logo.tsx +262 -0
- package/dist/theme/components/nav/theme-switcher.css +92 -0
- package/dist/theme/components/nav/theme-switcher.tsx +298 -0
- package/dist/theme/components/nav/topbar-cta.css +45 -0
- package/dist/theme/components/nav/topbar-cta.tsx +39 -0
- package/dist/theme/components/nav/variant-toggle.css +70 -0
- package/dist/theme/components/nav/variant-toggle.tsx +83 -0
- package/dist/theme/components/nav/version-chip.css +31 -0
- package/dist/theme/components/nav/version-chip.tsx +45 -0
- package/dist/theme/components/nav/vscode-tag.css +32 -0
- package/dist/theme/components/nav/vscode-tag.tsx +20 -0
- package/dist/theme/components/openapi/code-sample.tsx +341 -0
- package/dist/theme/components/openapi/copy-markdown-button.tsx +41 -0
- package/dist/theme/components/openapi/icons.tsx +45 -0
- package/dist/theme/components/openapi/index.ts +6 -0
- package/dist/theme/components/openapi/method-badge.tsx +24 -0
- package/dist/theme/components/openapi/openapi.css +665 -0
- package/dist/theme/components/openapi/operation.tsx +200 -0
- package/dist/theme/components/openapi/overview.tsx +207 -0
- package/dist/theme/components/openapi/parameters-table.tsx +149 -0
- package/dist/theme/components/openapi/request-body.tsx +90 -0
- package/dist/theme/components/openapi/response-list.tsx +112 -0
- package/dist/theme/components/openapi/schema-viewer.tsx +277 -0
- package/dist/theme/components/openapi/security-badges.tsx +63 -0
- package/dist/theme/components/openapi/spec-utils.ts +122 -0
- package/dist/theme/components/shared/accordion.css +118 -0
- package/dist/theme/components/shared/accordion.stories.tsx +96 -0
- package/dist/theme/components/shared/accordion.tsx +191 -0
- package/dist/theme/components/shared/brand-logo.stories.tsx +49 -0
- package/dist/theme/components/shared/brand-mark.stories.tsx +53 -0
- package/dist/theme/components/shared/browser-window.css +1 -0
- package/dist/theme/components/shared/browser-window.stories.tsx +100 -0
- package/dist/theme/components/shared/browser-window.tsx +2 -0
- package/dist/theme/components/shared/card.stories.tsx +50 -0
- package/dist/theme/components/shared/card.tsx +35 -0
- package/dist/theme/components/shared/ciderpress-logo.tsx +93 -0
- package/dist/theme/components/shared/ciderpress-mark.tsx +77 -0
- package/dist/theme/components/shared/color.css +56 -0
- package/dist/theme/components/shared/color.stories.tsx +47 -0
- package/dist/theme/components/shared/color.tsx +69 -0
- package/dist/theme/components/shared/columns.css +37 -0
- package/dist/theme/components/shared/columns.stories.tsx +91 -0
- package/dist/theme/components/shared/columns.tsx +50 -0
- package/dist/theme/components/shared/desktop-window.css +339 -0
- package/dist/theme/components/shared/desktop-window.tsx +535 -0
- package/dist/theme/components/shared/field.css +158 -0
- package/dist/theme/components/shared/field.stories.tsx +100 -0
- package/dist/theme/components/shared/field.tsx +186 -0
- package/dist/theme/components/shared/frame.css +39 -0
- package/dist/theme/components/shared/frame.stories.tsx +49 -0
- package/dist/theme/components/shared/frame.tsx +76 -0
- package/dist/theme/components/shared/icon.stories.tsx +90 -0
- package/dist/theme/components/shared/icon.tsx +41 -0
- package/dist/theme/components/shared/prompt.css +178 -0
- package/dist/theme/components/shared/prompt.stories.tsx +57 -0
- package/dist/theme/components/shared/prompt.tsx +369 -0
- package/dist/theme/components/shared/resolve-card-icon.ts +28 -0
- package/dist/theme/components/shared/section-card.stories.tsx +51 -0
- package/dist/theme/components/shared/section-card.tsx +44 -0
- package/dist/theme/components/shared/section-grid.stories.tsx +46 -0
- package/dist/theme/components/shared/section-grid.tsx +15 -0
- package/dist/theme/components/shared/status-badge.css +37 -0
- package/dist/theme/components/shared/status-badge.stories.tsx +61 -0
- package/dist/theme/components/shared/status-badge.tsx +74 -0
- package/dist/theme/components/shared/steps.css +88 -0
- package/dist/theme/components/shared/steps.stories.tsx +68 -0
- package/dist/theme/components/shared/steps.tsx +100 -0
- package/dist/theme/components/shared/tech-icon-table.stories.tsx +29 -0
- package/dist/theme/components/shared/tech-icon-table.tsx +71 -0
- package/dist/theme/components/shared/tech-tag.stories.tsx +40 -0
- package/dist/theme/components/shared/tech-tag.tsx +31 -0
- package/dist/theme/components/shared/theme-color-palette.css +80 -0
- package/dist/theme/components/shared/theme-color-palette.stories.tsx +52 -0
- package/dist/theme/components/shared/theme-color-palette.tsx +275 -0
- package/dist/theme/components/shared/tooltip.css +69 -0
- package/dist/theme/components/shared/tooltip.stories.tsx +72 -0
- package/dist/theme/components/shared/tooltip.tsx +77 -0
- package/dist/theme/components/sidebar/framework-picker.css +77 -0
- package/dist/theme/components/sidebar/framework-picker.tsx +78 -0
- package/dist/theme/components/sidebar/sidebar-filter.test.ts +140 -0
- package/dist/theme/components/sidebar/sidebar-filter.ts +79 -0
- package/dist/theme/components/sidebar/sidebar-links.css +102 -0
- package/dist/theme/components/sidebar/sidebar-links.tsx +122 -0
- package/dist/theme/components/sidebar/sidebar-promo.css +42 -0
- package/dist/theme/components/sidebar/sidebar-promo.tsx +52 -0
- package/dist/theme/components/sidebar/sidebar-scope.tsx +151 -0
- package/dist/theme/components/sidebar/sidebar-toggle.css +181 -0
- package/dist/theme/components/sidebar/sidebar-toggle.tsx +127 -0
- package/dist/theme/components/theme-provider.tsx +637 -0
- package/dist/theme/components/workspaces/card.css +151 -0
- package/dist/theme/components/workspaces/card.tsx +166 -0
- package/dist/theme/components/workspaces/grid.tsx +62 -0
- package/dist/theme/css.d.ts +1 -0
- package/dist/theme/env.d.ts +12 -0
- package/dist/theme/fonts/GeistMono-Variable.woff2 +0 -0
- package/dist/theme/fonts/GeistPixel-Square.woff2 +0 -0
- package/dist/theme/fonts/GeistSans-Variable.woff2 +0 -0
- package/dist/theme/hooks/use-ciderpress.ts +61 -0
- package/dist/theme/hooks/use-nav-items.ts +76 -0
- package/dist/theme/icons/index.ts +2 -0
- package/dist/theme/icons/tech-map.ts +228 -0
- package/dist/theme/index.tsx +180 -0
- package/dist/theme/lib/read-social-links.ts +27 -0
- package/dist/theme/lib/route-link.tsx +126 -0
- package/dist/theme/lib/safe-url.test.ts +0 -0
- package/dist/theme/lib/safe-url.ts +68 -0
- package/dist/theme/lib/theme-favicon.ts +125 -0
- package/dist/theme/lib/user-config-stub.ts +12 -0
- package/dist/theme/styles/layers.css +32 -0
- package/dist/theme/styles/overrides/badge.css +26 -0
- package/dist/theme/styles/overrides/content-footer.css +80 -0
- package/dist/theme/styles/overrides/details.css +61 -0
- package/dist/theme/styles/overrides/fonts.css +32 -0
- package/dist/theme/styles/overrides/home-card.css +133 -0
- package/dist/theme/styles/overrides/home.css +243 -0
- package/dist/theme/styles/overrides/rail.css +337 -0
- package/dist/theme/styles/overrides/rspress.css +62 -0
- package/dist/theme/styles/overrides/scrollbar.css +25 -0
- package/dist/theme/styles/overrides/section-card.css +114 -0
- package/dist/theme/styles/overrides/sidebar.css +144 -0
- package/dist/theme/styles/overrides/tokens.css +288 -0
- package/dist/theme/styles/overrides/vscode.css +88 -0
- package/dist/theme/styles/themes/amber.css +545 -0
- package/dist/theme/styles/themes/arcade-fx.css +366 -0
- package/dist/theme/styles/themes/arcade.css +273 -0
- package/dist/theme/styles/themes/grannysmith.css +545 -0
- package/dist/theme/styles/themes/honeycrisp.css +545 -0
- package/dist/theme/styles/themes/midnight.css +273 -0
- package/dist/theme/styles/themes/mulled.css +817 -0
- package/package.json +103 -0
- package/src/theme/components/announcement/announcement-bar.css +103 -0
- package/src/theme/components/announcement/announcement-bar.tsx +173 -0
- package/src/theme/components/ask-ai/ask-ai-button.css +52 -0
- package/src/theme/components/ask-ai/ask-ai-button.tsx +56 -0
- package/src/theme/components/content-footer/content-footer-portal.tsx +70 -0
- package/src/theme/components/content-footer/feedback.css +47 -0
- package/src/theme/components/content-footer/feedback.tsx +96 -0
- package/src/theme/components/content-footer/meta-actions.css +25 -0
- package/src/theme/components/content-footer/meta-actions.tsx +57 -0
- package/src/theme/components/content-footer/page-pager.css +61 -0
- package/src/theme/components/content-footer/page-pager.tsx +110 -0
- package/src/theme/components/edit-source-button.tsx +97 -0
- package/src/theme/components/footer/site-footer.css +179 -0
- package/src/theme/components/footer/site-footer.tsx +115 -0
- package/src/theme/components/home/cta.css +67 -0
- package/src/theme/components/home/cta.tsx +77 -0
- package/src/theme/components/home/feature-card.css +52 -0
- package/src/theme/components/home/feature-card.tsx +131 -0
- package/src/theme/components/home/feature.tsx +79 -0
- package/src/theme/components/home/hero-demo.css +66 -0
- package/src/theme/components/home/hero-demo.tsx +46 -0
- package/src/theme/components/home/hero.css +96 -0
- package/src/theme/components/home/hero.tsx +111 -0
- package/src/theme/components/home/layout.tsx +183 -0
- package/src/theme/components/home/page-rail.css +21 -0
- package/src/theme/components/home/page-rail.tsx +19 -0
- package/src/theme/components/home/split.css +148 -0
- package/src/theme/components/home/split.tsx +120 -0
- package/src/theme/components/home/trust-strip.css +33 -0
- package/src/theme/components/home/trust-strip.tsx +43 -0
- package/src/theme/components/home/workspaces.tsx +71 -0
- package/src/theme/components/nav/branch-tag.css +54 -0
- package/src/theme/components/nav/branch-tag.tsx +101 -0
- package/src/theme/components/nav/ciderpress-doc-layout.tsx +247 -0
- package/src/theme/components/nav/ciderpress-docs-bar.css +505 -0
- package/src/theme/components/nav/ciderpress-docs-bar.tsx +426 -0
- package/src/theme/components/nav/ciderpress-header.css +172 -0
- package/src/theme/components/nav/ciderpress-header.tsx +113 -0
- package/src/theme/components/nav/ciderpress-layout-context.tsx +49 -0
- package/src/theme/components/nav/ciderpress-nav-hamburger.css +193 -0
- package/src/theme/components/nav/ciderpress-nav-hamburger.tsx +112 -0
- package/src/theme/components/nav/ciderpress-nav-menu.css +130 -0
- package/src/theme/components/nav/ciderpress-nav-menu.tsx +372 -0
- package/src/theme/components/nav/ciderpress-nav-search.css +73 -0
- package/src/theme/components/nav/ciderpress-nav-search.tsx +55 -0
- package/src/theme/components/nav/ciderpress-nav-social-links.css +35 -0
- package/src/theme/components/nav/ciderpress-nav-social-links.tsx +87 -0
- package/src/theme/components/nav/floating-branch-indicator.css +106 -0
- package/src/theme/components/nav/floating-branch-indicator.tsx +109 -0
- package/src/theme/components/nav/layout.tsx +268 -0
- package/src/theme/components/nav/mobile-nav-cta.tsx +79 -0
- package/src/theme/components/nav/nav-divider.css +15 -0
- package/src/theme/components/nav/nav-divider.tsx +19 -0
- package/src/theme/components/nav/nav-logo.css +42 -0
- package/src/theme/components/nav/nav-logo.tsx +262 -0
- package/src/theme/components/nav/theme-switcher.css +92 -0
- package/src/theme/components/nav/theme-switcher.tsx +298 -0
- package/src/theme/components/nav/topbar-cta.css +45 -0
- package/src/theme/components/nav/topbar-cta.tsx +39 -0
- package/src/theme/components/nav/variant-toggle.css +70 -0
- package/src/theme/components/nav/variant-toggle.tsx +83 -0
- package/src/theme/components/nav/version-chip.css +31 -0
- package/src/theme/components/nav/version-chip.tsx +45 -0
- package/src/theme/components/nav/vscode-tag.css +32 -0
- package/src/theme/components/nav/vscode-tag.tsx +20 -0
- package/src/theme/components/openapi/code-sample.tsx +341 -0
- package/src/theme/components/openapi/copy-markdown-button.tsx +41 -0
- package/src/theme/components/openapi/icons.tsx +45 -0
- package/src/theme/components/openapi/index.ts +6 -0
- package/src/theme/components/openapi/method-badge.tsx +24 -0
- package/src/theme/components/openapi/openapi.css +665 -0
- package/src/theme/components/openapi/operation.tsx +200 -0
- package/src/theme/components/openapi/overview.tsx +207 -0
- package/src/theme/components/openapi/parameters-table.tsx +149 -0
- package/src/theme/components/openapi/request-body.tsx +90 -0
- package/src/theme/components/openapi/response-list.tsx +112 -0
- package/src/theme/components/openapi/schema-viewer.tsx +277 -0
- package/src/theme/components/openapi/security-badges.tsx +63 -0
- package/src/theme/components/openapi/spec-utils.ts +122 -0
- package/src/theme/components/shared/accordion.css +118 -0
- package/src/theme/components/shared/accordion.stories.tsx +96 -0
- package/src/theme/components/shared/accordion.tsx +191 -0
- package/src/theme/components/shared/brand-logo.stories.tsx +49 -0
- package/src/theme/components/shared/brand-mark.stories.tsx +53 -0
- package/src/theme/components/shared/browser-window.css +1 -0
- package/src/theme/components/shared/browser-window.stories.tsx +100 -0
- package/src/theme/components/shared/browser-window.tsx +2 -0
- package/src/theme/components/shared/card.stories.tsx +50 -0
- package/src/theme/components/shared/card.tsx +35 -0
- package/src/theme/components/shared/ciderpress-logo.tsx +93 -0
- package/src/theme/components/shared/ciderpress-mark.tsx +77 -0
- package/src/theme/components/shared/color.css +56 -0
- package/src/theme/components/shared/color.stories.tsx +47 -0
- package/src/theme/components/shared/color.tsx +69 -0
- package/src/theme/components/shared/columns.css +37 -0
- package/src/theme/components/shared/columns.stories.tsx +91 -0
- package/src/theme/components/shared/columns.tsx +50 -0
- package/src/theme/components/shared/desktop-window.css +339 -0
- package/src/theme/components/shared/desktop-window.tsx +535 -0
- package/src/theme/components/shared/field.css +158 -0
- package/src/theme/components/shared/field.stories.tsx +100 -0
- package/src/theme/components/shared/field.tsx +186 -0
- package/src/theme/components/shared/frame.css +39 -0
- package/src/theme/components/shared/frame.stories.tsx +49 -0
- package/src/theme/components/shared/frame.tsx +76 -0
- package/src/theme/components/shared/icon.stories.tsx +90 -0
- package/src/theme/components/shared/icon.tsx +41 -0
- package/src/theme/components/shared/prompt.css +178 -0
- package/src/theme/components/shared/prompt.stories.tsx +57 -0
- package/src/theme/components/shared/prompt.tsx +369 -0
- package/src/theme/components/shared/resolve-card-icon.ts +28 -0
- package/src/theme/components/shared/section-card.stories.tsx +51 -0
- package/src/theme/components/shared/section-card.tsx +44 -0
- package/src/theme/components/shared/section-grid.stories.tsx +46 -0
- package/src/theme/components/shared/section-grid.tsx +15 -0
- package/src/theme/components/shared/status-badge.css +37 -0
- package/src/theme/components/shared/status-badge.stories.tsx +61 -0
- package/src/theme/components/shared/status-badge.tsx +74 -0
- package/src/theme/components/shared/steps.css +88 -0
- package/src/theme/components/shared/steps.stories.tsx +68 -0
- package/src/theme/components/shared/steps.tsx +100 -0
- package/src/theme/components/shared/tech-icon-table.stories.tsx +29 -0
- package/src/theme/components/shared/tech-icon-table.tsx +71 -0
- package/src/theme/components/shared/tech-tag.stories.tsx +40 -0
- package/src/theme/components/shared/tech-tag.tsx +31 -0
- package/src/theme/components/shared/theme-color-palette.css +80 -0
- package/src/theme/components/shared/theme-color-palette.stories.tsx +52 -0
- package/src/theme/components/shared/theme-color-palette.tsx +275 -0
- package/src/theme/components/shared/tooltip.css +69 -0
- package/src/theme/components/shared/tooltip.stories.tsx +72 -0
- package/src/theme/components/shared/tooltip.tsx +77 -0
- package/src/theme/components/sidebar/framework-picker.css +77 -0
- package/src/theme/components/sidebar/framework-picker.tsx +78 -0
- package/src/theme/components/sidebar/sidebar-filter.test.ts +140 -0
- package/src/theme/components/sidebar/sidebar-filter.ts +79 -0
- package/src/theme/components/sidebar/sidebar-links.css +102 -0
- package/src/theme/components/sidebar/sidebar-links.tsx +122 -0
- package/src/theme/components/sidebar/sidebar-promo.css +42 -0
- package/src/theme/components/sidebar/sidebar-promo.tsx +52 -0
- package/src/theme/components/sidebar/sidebar-scope.tsx +151 -0
- package/src/theme/components/sidebar/sidebar-toggle.css +181 -0
- package/src/theme/components/sidebar/sidebar-toggle.tsx +127 -0
- package/src/theme/components/theme-provider.tsx +637 -0
- package/src/theme/components/workspaces/card.css +151 -0
- package/src/theme/components/workspaces/card.tsx +166 -0
- package/src/theme/components/workspaces/grid.tsx +62 -0
- package/src/theme/css.d.ts +1 -0
- package/src/theme/env.d.ts +12 -0
- package/src/theme/fonts/GeistMono-Variable.woff2 +0 -0
- package/src/theme/fonts/GeistPixel-Square.woff2 +0 -0
- package/src/theme/fonts/GeistSans-Variable.woff2 +0 -0
- package/src/theme/hooks/use-ciderpress.ts +61 -0
- package/src/theme/hooks/use-nav-items.ts +76 -0
- package/src/theme/icons/index.ts +2 -0
- package/src/theme/icons/tech-map.ts +228 -0
- package/src/theme/index.tsx +180 -0
- package/src/theme/lib/read-social-links.ts +27 -0
- package/src/theme/lib/route-link.tsx +126 -0
- package/src/theme/lib/safe-url.test.ts +0 -0
- package/src/theme/lib/safe-url.ts +68 -0
- package/src/theme/lib/theme-favicon.ts +125 -0
- package/src/theme/lib/user-config-stub.ts +12 -0
- package/src/theme/styles/layers.css +32 -0
- package/src/theme/styles/overrides/badge.css +26 -0
- package/src/theme/styles/overrides/content-footer.css +80 -0
- package/src/theme/styles/overrides/details.css +61 -0
- package/src/theme/styles/overrides/fonts.css +32 -0
- package/src/theme/styles/overrides/home-card.css +133 -0
- package/src/theme/styles/overrides/home.css +243 -0
- package/src/theme/styles/overrides/rail.css +337 -0
- package/src/theme/styles/overrides/rspress.css +62 -0
- package/src/theme/styles/overrides/scrollbar.css +25 -0
- package/src/theme/styles/overrides/section-card.css +114 -0
- package/src/theme/styles/overrides/sidebar.css +144 -0
- package/src/theme/styles/overrides/tokens.css +288 -0
- package/src/theme/styles/overrides/vscode.css +88 -0
- package/src/theme/styles/themes/amber.css +545 -0
- package/src/theme/styles/themes/arcade-fx.css +366 -0
- package/src/theme/styles/themes/arcade.css +273 -0
- package/src/theme/styles/themes/grannysmith.css +545 -0
- package/src/theme/styles/themes/honeycrisp.css +545 -0
- package/src/theme/styles/themes/midnight.css +273 -0
- package/src/theme/styles/themes/mulled.css +817 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
.cp-announce {
|
|
2
|
+
position: relative;
|
|
3
|
+
height: var(--cp-announce-h);
|
|
4
|
+
display: flex;
|
|
5
|
+
align-items: center;
|
|
6
|
+
justify-content: center;
|
|
7
|
+
gap: 12px;
|
|
8
|
+
padding: 0 48px;
|
|
9
|
+
background: linear-gradient(
|
|
10
|
+
90deg,
|
|
11
|
+
var(--cp-c-brand-soft) 0%,
|
|
12
|
+
transparent 50%,
|
|
13
|
+
var(--cp-c-brand-soft) 100%
|
|
14
|
+
);
|
|
15
|
+
border-bottom: 1px solid var(--cp-c-border);
|
|
16
|
+
font-size: 12.5px;
|
|
17
|
+
color: var(--cp-c-text-2);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.cp-announce__pulse {
|
|
21
|
+
width: 6px;
|
|
22
|
+
height: 6px;
|
|
23
|
+
border-radius: 50%;
|
|
24
|
+
background: var(--cp-c-brand-1);
|
|
25
|
+
animation: cp-announce-pulse 2.4s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
@keyframes cp-announce-pulse {
|
|
29
|
+
0%,
|
|
30
|
+
100% {
|
|
31
|
+
box-shadow: 0 0 0 0 var(--cp-c-brand-soft);
|
|
32
|
+
}
|
|
33
|
+
50% {
|
|
34
|
+
box-shadow: 0 0 0 6px transparent;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.cp-announce__msg {
|
|
39
|
+
display: inline-flex;
|
|
40
|
+
flex-wrap: wrap;
|
|
41
|
+
gap: 4px;
|
|
42
|
+
align-items: center;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.cp-announce__lead {
|
|
46
|
+
color: var(--cp-c-text-1);
|
|
47
|
+
font-style: normal;
|
|
48
|
+
font-weight: 500;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.cp-announce__cta {
|
|
52
|
+
color: var(--cp-c-brand-1);
|
|
53
|
+
font-weight: 500;
|
|
54
|
+
margin-left: 4px;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.cp-announce__cta:hover {
|
|
58
|
+
color: var(--cp-c-brand-2);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.cp-announce__close {
|
|
62
|
+
position: absolute;
|
|
63
|
+
right: 12px;
|
|
64
|
+
top: 50%;
|
|
65
|
+
transform: translateY(-50%);
|
|
66
|
+
width: 22px;
|
|
67
|
+
height: 22px;
|
|
68
|
+
display: grid;
|
|
69
|
+
place-items: center;
|
|
70
|
+
background: transparent;
|
|
71
|
+
border: 0;
|
|
72
|
+
border-radius: 4px;
|
|
73
|
+
color: var(--cp-c-text-3);
|
|
74
|
+
cursor: pointer;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.cp-announce__close:hover {
|
|
78
|
+
background: var(--cp-c-bg-soft);
|
|
79
|
+
color: var(--cp-c-text-1);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/* Mobile: shrink padding so the message doesn't wrap weirdly inside a
|
|
83
|
+
fixed 36px row. Let the bar grow vertically when the message + CTA
|
|
84
|
+
would otherwise collide with the close button. */
|
|
85
|
+
@media (max-width: 720px) {
|
|
86
|
+
html .cp-announce {
|
|
87
|
+
height: auto;
|
|
88
|
+
min-height: var(--cp-announce-h);
|
|
89
|
+
padding: 6px 36px 6px 12px;
|
|
90
|
+
font-size: 12px;
|
|
91
|
+
line-height: 1.35;
|
|
92
|
+
text-align: left;
|
|
93
|
+
justify-content: flex-start;
|
|
94
|
+
}
|
|
95
|
+
html .cp-announce__msg {
|
|
96
|
+
flex: 1;
|
|
97
|
+
flex-wrap: wrap;
|
|
98
|
+
row-gap: 2px;
|
|
99
|
+
}
|
|
100
|
+
html .cp-announce__close {
|
|
101
|
+
right: 6px;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { match } from 'massaman/match'
|
|
2
|
+
import React, { useCallback, useEffect, useRef, useState } from 'react'
|
|
3
|
+
|
|
4
|
+
import { RouteLink } from '../../lib/route-link.tsx'
|
|
5
|
+
import { safeUrl } from '../../lib/safe-url.ts'
|
|
6
|
+
|
|
7
|
+
import './announcement-bar.css'
|
|
8
|
+
|
|
9
|
+
const STORAGE_PREFIX = 'ciderpress-announcement-dismissed:'
|
|
10
|
+
/**
|
|
11
|
+
* Initial height assumption before the bar mounts and measures itself.
|
|
12
|
+
* Matches the value Rspress's own `<Banner />` uses (`useState(36)`) so
|
|
13
|
+
* downstream sticky offsets are roughly correct on the first paint.
|
|
14
|
+
*/
|
|
15
|
+
const INITIAL_HEIGHT = 36
|
|
16
|
+
|
|
17
|
+
export interface AnnouncementBarProps {
|
|
18
|
+
/**
|
|
19
|
+
* Stable ID — when present, dismissal is remembered in localStorage.
|
|
20
|
+
*/
|
|
21
|
+
readonly id?: string
|
|
22
|
+
/**
|
|
23
|
+
* Lead text. Use for the highlighted phrase ("ciderpress 1.0", "NEW:", etc).
|
|
24
|
+
*/
|
|
25
|
+
readonly lead?: React.ReactNode
|
|
26
|
+
/**
|
|
27
|
+
* Body of the announcement.
|
|
28
|
+
*/
|
|
29
|
+
readonly children: React.ReactNode
|
|
30
|
+
/**
|
|
31
|
+
* Optional CTA link rendered after the message.
|
|
32
|
+
*/
|
|
33
|
+
readonly cta?: { readonly href: string; readonly label: string }
|
|
34
|
+
/**
|
|
35
|
+
* Hide the dismiss button.
|
|
36
|
+
*/
|
|
37
|
+
readonly persistent?: boolean
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* AnnouncementBar — full-width banner rendered above the topbar via the
|
|
42
|
+
* Layout `top` slot. Ships with a pulsing accent dot and an optional
|
|
43
|
+
* dismiss button that persists in localStorage when an `id` is provided.
|
|
44
|
+
*
|
|
45
|
+
* @param props - AnnouncementBar configuration.
|
|
46
|
+
* @returns React element, or null when the bar has been dismissed.
|
|
47
|
+
*/
|
|
48
|
+
export function AnnouncementBar(props: AnnouncementBarProps): React.ReactElement | null {
|
|
49
|
+
const { id, lead, children, cta, persistent } = props
|
|
50
|
+
const [dismissed, setDismissed] = useState(() => readDismissed(id))
|
|
51
|
+
// Track the rendered height so downstream sticky offsets
|
|
52
|
+
// (`--rp-nav-height` calc + the docs bar's sticky `top`) clear the
|
|
53
|
+
// announcement bar without anybody hardcoding a number. Mirrors the
|
|
54
|
+
// pattern Rspress's own `<Banner />` uses: a state value + a
|
|
55
|
+
// callback ref + a sibling `<style>` tag exposing the value as a
|
|
56
|
+
// CSS variable.
|
|
57
|
+
const [height, setHeight] = useState(INITIAL_HEIGHT)
|
|
58
|
+
const measureRef = useRef<HTMLDivElement | null>(null)
|
|
59
|
+
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
setDismissed(readDismissed(id))
|
|
62
|
+
}, [id])
|
|
63
|
+
|
|
64
|
+
const handleDismiss = useCallback(() => {
|
|
65
|
+
writeDismissed(id)
|
|
66
|
+
setDismissed(true)
|
|
67
|
+
}, [id])
|
|
68
|
+
|
|
69
|
+
const setMeasureRef = useCallback((element: HTMLDivElement | null) => {
|
|
70
|
+
measureRef.current = element
|
|
71
|
+
if (element !== null && element.offsetHeight > 0) {
|
|
72
|
+
setHeight(element.offsetHeight)
|
|
73
|
+
}
|
|
74
|
+
}, [])
|
|
75
|
+
|
|
76
|
+
return match(dismissed)
|
|
77
|
+
.with(true, () => null)
|
|
78
|
+
.otherwise(() => (
|
|
79
|
+
<>
|
|
80
|
+
<div ref={setMeasureRef} className="cp-announce" role="region" aria-label="Announcement">
|
|
81
|
+
<span className="cp-announce__pulse" aria-hidden="true" />
|
|
82
|
+
<span className="cp-announce__msg">
|
|
83
|
+
{match(lead)
|
|
84
|
+
.with(undefined, () => null)
|
|
85
|
+
.otherwise((l) => (
|
|
86
|
+
<em className="cp-announce__lead">{l}</em>
|
|
87
|
+
))}{' '}
|
|
88
|
+
{children}
|
|
89
|
+
{match(cta)
|
|
90
|
+
.with(undefined, () => null)
|
|
91
|
+
.otherwise((c) => {
|
|
92
|
+
const href = safeUrl(c.href)
|
|
93
|
+
if (href === null) {
|
|
94
|
+
return null
|
|
95
|
+
}
|
|
96
|
+
return (
|
|
97
|
+
<RouteLink className="cp-announce__cta" href={href}>
|
|
98
|
+
{c.label} →
|
|
99
|
+
</RouteLink>
|
|
100
|
+
)
|
|
101
|
+
})}
|
|
102
|
+
</span>
|
|
103
|
+
{match(persistent === true)
|
|
104
|
+
.with(true, () => null)
|
|
105
|
+
.otherwise(() => (
|
|
106
|
+
<button
|
|
107
|
+
className="cp-announce__close"
|
|
108
|
+
type="button"
|
|
109
|
+
onClick={handleDismiss}
|
|
110
|
+
aria-label="Dismiss announcement"
|
|
111
|
+
>
|
|
112
|
+
<svg
|
|
113
|
+
width="12"
|
|
114
|
+
height="12"
|
|
115
|
+
viewBox="0 0 24 24"
|
|
116
|
+
fill="none"
|
|
117
|
+
stroke="currentColor"
|
|
118
|
+
strokeWidth="2"
|
|
119
|
+
>
|
|
120
|
+
<line x1="18" y1="6" x2="6" y2="18" />
|
|
121
|
+
<line x1="6" y1="6" x2="18" y2="18" />
|
|
122
|
+
</svg>
|
|
123
|
+
</button>
|
|
124
|
+
))}
|
|
125
|
+
</div>
|
|
126
|
+
{/* Expose the measured height as a CSS variable so the docs
|
|
127
|
+
bar's sticky `top:` and the `--rp-nav-height` calc clear
|
|
128
|
+
the announcement without anybody hardcoding 36px. Pattern
|
|
129
|
+
cribbed directly from Rspress's `<Banner />`. */}
|
|
130
|
+
<style>{`:root { --cp-announcement-height: ${height}px; }`}</style>
|
|
131
|
+
</>
|
|
132
|
+
))
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Read the dismissed flag from localStorage.
|
|
137
|
+
*
|
|
138
|
+
* @private
|
|
139
|
+
* @param id - Announcement ID, or undefined.
|
|
140
|
+
* @returns true when the announcement has been dismissed.
|
|
141
|
+
*/
|
|
142
|
+
function readDismissed(id: string | undefined): boolean {
|
|
143
|
+
return match(id)
|
|
144
|
+
.with(undefined, () => false)
|
|
145
|
+
.otherwise((key) => {
|
|
146
|
+
if (globalThis.window === undefined) {
|
|
147
|
+
return false
|
|
148
|
+
}
|
|
149
|
+
try {
|
|
150
|
+
return globalThis.localStorage.getItem(STORAGE_PREFIX + key) === '1'
|
|
151
|
+
} catch {
|
|
152
|
+
return false
|
|
153
|
+
}
|
|
154
|
+
})
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Persist the dismissed flag to localStorage.
|
|
159
|
+
*
|
|
160
|
+
* @private
|
|
161
|
+
* @param id - Announcement ID, or undefined.
|
|
162
|
+
*/
|
|
163
|
+
function writeDismissed(id: string | undefined): void {
|
|
164
|
+
match(id)
|
|
165
|
+
.with(undefined, () => {})
|
|
166
|
+
.otherwise((key) => {
|
|
167
|
+
try {
|
|
168
|
+
globalThis.localStorage.setItem(STORAGE_PREFIX + key, '1')
|
|
169
|
+
} catch {
|
|
170
|
+
// ignore — storage may be unavailable
|
|
171
|
+
}
|
|
172
|
+
})
|
|
173
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
.cp-ask-ai {
|
|
2
|
+
position: fixed;
|
|
3
|
+
bottom: var(--cp-s-20);
|
|
4
|
+
right: var(--cp-s-20);
|
|
5
|
+
z-index: var(--cp-z-floating);
|
|
6
|
+
display: inline-flex;
|
|
7
|
+
align-items: center;
|
|
8
|
+
gap: var(--cp-s-8);
|
|
9
|
+
padding: var(--cp-s-10) var(--cp-s-14);
|
|
10
|
+
background: var(--cp-c-bg-soft);
|
|
11
|
+
border: 1px solid var(--cp-c-border);
|
|
12
|
+
border-radius: var(--cp-radius-pill);
|
|
13
|
+
color: var(--cp-c-text-1);
|
|
14
|
+
font-family: var(--cp-font-family-base);
|
|
15
|
+
font-size: var(--cp-fs-ask-ai);
|
|
16
|
+
font-weight: 500;
|
|
17
|
+
cursor: pointer;
|
|
18
|
+
box-shadow: var(--cp-shadow-ask-ai);
|
|
19
|
+
backdrop-filter: blur(var(--cp-blur-base));
|
|
20
|
+
transition: border-color var(--cp-duration-fast) var(--cp-ease-base);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.cp-ask-ai:hover {
|
|
24
|
+
border-color: var(--cp-c-brand-soft);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.cp-ask-ai__icon {
|
|
28
|
+
width: var(--cp-size-ask-ai-icon);
|
|
29
|
+
height: var(--cp-size-ask-ai-icon);
|
|
30
|
+
border-radius: 50%;
|
|
31
|
+
background: var(--cp-gradient-brand);
|
|
32
|
+
display: grid;
|
|
33
|
+
place-items: center;
|
|
34
|
+
color: var(--cp-c-brand-fg);
|
|
35
|
+
font-family: var(--cp-font-family-mono);
|
|
36
|
+
font-size: var(--cp-fs-ask-ai-mark);
|
|
37
|
+
font-weight: 700;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.cp-ask-ai__label {
|
|
41
|
+
white-space: nowrap;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.cp-ask-ai__shortcut {
|
|
45
|
+
font-family: var(--cp-font-family-mono);
|
|
46
|
+
font-size: var(--cp-fs-ask-ai-shortcut);
|
|
47
|
+
color: var(--cp-c-text-3);
|
|
48
|
+
padding: var(--cp-s-1) var(--cp-s-5);
|
|
49
|
+
border: 1px solid var(--cp-c-border);
|
|
50
|
+
border-radius: var(--cp-radius-xs-sm);
|
|
51
|
+
margin-left: var(--cp-s-4);
|
|
52
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { match } from 'massaman/match'
|
|
2
|
+
import React, { useCallback } from 'react'
|
|
3
|
+
|
|
4
|
+
import './ask-ai-button.css'
|
|
5
|
+
|
|
6
|
+
export interface AskAIButtonProps {
|
|
7
|
+
/**
|
|
8
|
+
* Click handler. When omitted, the button does nothing.
|
|
9
|
+
*/
|
|
10
|
+
readonly onClick?: () => void
|
|
11
|
+
/**
|
|
12
|
+
* Visible label. Defaults to "Ask AI".
|
|
13
|
+
*/
|
|
14
|
+
readonly label?: string
|
|
15
|
+
/**
|
|
16
|
+
* Optional shortcut hint (e.g. "⌘J").
|
|
17
|
+
*/
|
|
18
|
+
readonly shortcut?: string
|
|
19
|
+
/**
|
|
20
|
+
* Pre-positioned glyph for the badge (default: ★).
|
|
21
|
+
*/
|
|
22
|
+
readonly badge?: React.ReactNode
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* AskAIButton — floating CTA pill anchored bottom-right of the viewport.
|
|
27
|
+
* Visual sibling of the page chrome; pairs with AI-search integrations.
|
|
28
|
+
*
|
|
29
|
+
* @param props - AskAIButton configuration.
|
|
30
|
+
* @returns React element.
|
|
31
|
+
*/
|
|
32
|
+
export function AskAIButton(props: AskAIButtonProps): React.ReactElement {
|
|
33
|
+
const { onClick, label = 'Ask AI', shortcut, badge = '★' } = props
|
|
34
|
+
|
|
35
|
+
const handleClick = useCallback(() => {
|
|
36
|
+
match(onClick)
|
|
37
|
+
.with(undefined, () => {})
|
|
38
|
+
.otherwise((fn) => {
|
|
39
|
+
fn()
|
|
40
|
+
})
|
|
41
|
+
}, [onClick])
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<button className="cp-ask-ai" type="button" onClick={handleClick} aria-label={label}>
|
|
45
|
+
<span className="cp-ask-ai__icon" aria-hidden="true">
|
|
46
|
+
{badge}
|
|
47
|
+
</span>
|
|
48
|
+
<span className="cp-ask-ai__label">{label}</span>
|
|
49
|
+
{match(shortcut)
|
|
50
|
+
.with(undefined, () => null)
|
|
51
|
+
.otherwise((s) => (
|
|
52
|
+
<span className="cp-ask-ai__shortcut">{s}</span>
|
|
53
|
+
))}
|
|
54
|
+
</button>
|
|
55
|
+
)
|
|
56
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react'
|
|
2
|
+
import { createPortal } from 'react-dom'
|
|
3
|
+
|
|
4
|
+
export interface ContentFooterPortalProps {
|
|
5
|
+
/**
|
|
6
|
+
* Children to portal into the in-rail doc footer slot.
|
|
7
|
+
*/
|
|
8
|
+
readonly children: React.ReactNode
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const HOST_ATTR = 'data-cp-content-footer-host'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* ContentFooterPortal — portals its children into Rspress's `.rp-doc-footer`
|
|
15
|
+
* so the content lives INSIDE the doc rail (at the reading width) rather
|
|
16
|
+
* than at body level (where the `afterDoc` slot otherwise lands).
|
|
17
|
+
*
|
|
18
|
+
* Inserts a sentinel div as the FIRST child of `.rp-doc-footer` so the
|
|
19
|
+
* portalled content appears above Rspress's built-in `.rp-prev-next-page`
|
|
20
|
+
* pager. Watches DOM mutations so the portal re-attaches on client-side
|
|
21
|
+
* route changes (Rspress re-renders the doc-footer per page).
|
|
22
|
+
*
|
|
23
|
+
* @param props - Portal configuration.
|
|
24
|
+
* @returns React element.
|
|
25
|
+
*/
|
|
26
|
+
export function ContentFooterPortal(props: ContentFooterPortalProps): React.ReactElement | null {
|
|
27
|
+
const [host, setHost] = useState<HTMLElement | null>(null)
|
|
28
|
+
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
function ensureHost(): HTMLElement | null {
|
|
31
|
+
const docFooter = globalThis.document.querySelector('.rp-doc-footer')
|
|
32
|
+
if (docFooter === null) {
|
|
33
|
+
return null
|
|
34
|
+
}
|
|
35
|
+
const existing = docFooter.querySelector(`[${HOST_ATTR}]`) as HTMLElement | null
|
|
36
|
+
if (existing !== null) {
|
|
37
|
+
return existing
|
|
38
|
+
}
|
|
39
|
+
const node = globalThis.document.createElement('div')
|
|
40
|
+
node.setAttribute(HOST_ATTR, '')
|
|
41
|
+
const first = docFooter.firstChild
|
|
42
|
+
if (first === null) {
|
|
43
|
+
docFooter.append(node)
|
|
44
|
+
} else {
|
|
45
|
+
first.before(node)
|
|
46
|
+
}
|
|
47
|
+
return node
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
setHost(ensureHost())
|
|
51
|
+
|
|
52
|
+
const observer = new globalThis.MutationObserver(() => {
|
|
53
|
+
const next = ensureHost()
|
|
54
|
+
setHost((prev) => {
|
|
55
|
+
if (prev !== null && prev.isConnected) {
|
|
56
|
+
return prev
|
|
57
|
+
}
|
|
58
|
+
return next
|
|
59
|
+
})
|
|
60
|
+
})
|
|
61
|
+
observer.observe(globalThis.document.body, { childList: true, subtree: true })
|
|
62
|
+
|
|
63
|
+
return () => observer.disconnect()
|
|
64
|
+
}, [])
|
|
65
|
+
|
|
66
|
+
if (host === null) {
|
|
67
|
+
return null
|
|
68
|
+
}
|
|
69
|
+
return createPortal(<div className="cp-content-footer">{props.children}</div>, host)
|
|
70
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
.cp-feedback {
|
|
2
|
+
display: flex;
|
|
3
|
+
align-items: center;
|
|
4
|
+
justify-content: space-between;
|
|
5
|
+
padding: 14px 16px;
|
|
6
|
+
border: 1px solid var(--cp-c-border);
|
|
7
|
+
border-radius: var(--cp-radius-card);
|
|
8
|
+
margin-bottom: 24px;
|
|
9
|
+
background: var(--cp-c-bg-soft);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.cp-feedback__q {
|
|
13
|
+
font-size: 13px;
|
|
14
|
+
color: var(--cp-c-text-1);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.cp-feedback__actions {
|
|
18
|
+
display: flex;
|
|
19
|
+
gap: 6px;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.cp-feedback__btn {
|
|
23
|
+
display: inline-flex;
|
|
24
|
+
align-items: center;
|
|
25
|
+
gap: 6px;
|
|
26
|
+
padding: 6px 12px;
|
|
27
|
+
font-size: 12px;
|
|
28
|
+
font-weight: 500;
|
|
29
|
+
font-family: inherit;
|
|
30
|
+
border-radius: var(--cp-radius-pill);
|
|
31
|
+
border: 1px solid var(--cp-c-border);
|
|
32
|
+
background: transparent;
|
|
33
|
+
color: var(--cp-c-text-1);
|
|
34
|
+
cursor: pointer;
|
|
35
|
+
transition: all 0.15s ease;
|
|
36
|
+
white-space: nowrap;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.cp-feedback__btn:hover {
|
|
40
|
+
background: var(--cp-c-bg-elv);
|
|
41
|
+
border-color: var(--cp-c-border);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.cp-feedback__thanks {
|
|
45
|
+
font-size: 13px;
|
|
46
|
+
color: var(--cp-c-text-2);
|
|
47
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { match } from 'massaman/match'
|
|
2
|
+
import React, { useCallback, useState } from 'react'
|
|
3
|
+
|
|
4
|
+
import './feedback.css'
|
|
5
|
+
|
|
6
|
+
export interface FeedbackProps {
|
|
7
|
+
/**
|
|
8
|
+
* Question shown to the reader. Defaults to "Was this page helpful?".
|
|
9
|
+
*/
|
|
10
|
+
readonly question?: string
|
|
11
|
+
/**
|
|
12
|
+
* Optional callback invoked with the user's answer.
|
|
13
|
+
*/
|
|
14
|
+
readonly onSubmit?: (answer: 'yes' | 'no') => void
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
type FeedbackState = 'idle' | 'yes' | 'no'
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Feedback — yes/no widget rendered at the bottom of doc pages.
|
|
21
|
+
* Submitting hides the buttons and shows a thank-you message.
|
|
22
|
+
*
|
|
23
|
+
* @param props - Feedback configuration.
|
|
24
|
+
* @returns React element.
|
|
25
|
+
*/
|
|
26
|
+
export function Feedback(props: FeedbackProps): React.ReactElement {
|
|
27
|
+
const { question = 'Was this page helpful?', onSubmit } = props
|
|
28
|
+
const [state, setState] = useState<FeedbackState>('idle')
|
|
29
|
+
|
|
30
|
+
const handleClick = useCallback(
|
|
31
|
+
(answer: 'yes' | 'no') => () => {
|
|
32
|
+
setState(answer)
|
|
33
|
+
match(onSubmit)
|
|
34
|
+
.with(undefined, () => {})
|
|
35
|
+
.otherwise((fn) => {
|
|
36
|
+
fn(answer)
|
|
37
|
+
})
|
|
38
|
+
},
|
|
39
|
+
[onSubmit]
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<div className="cp-feedback">
|
|
44
|
+
{match(state)
|
|
45
|
+
.with('idle', () => (
|
|
46
|
+
<>
|
|
47
|
+
<span className="cp-feedback__q">{question}</span>
|
|
48
|
+
<div className="cp-feedback__actions">
|
|
49
|
+
<button className="cp-feedback__btn" type="button" onClick={handleClick('yes')}>
|
|
50
|
+
<ThumbIcon />
|
|
51
|
+
Yes
|
|
52
|
+
</button>
|
|
53
|
+
<button className="cp-feedback__btn" type="button" onClick={handleClick('no')}>
|
|
54
|
+
<ThumbIcon flipped />
|
|
55
|
+
No
|
|
56
|
+
</button>
|
|
57
|
+
</div>
|
|
58
|
+
</>
|
|
59
|
+
))
|
|
60
|
+
.otherwise(() => (
|
|
61
|
+
<span className="cp-feedback__thanks">Thanks for the feedback.</span>
|
|
62
|
+
))}
|
|
63
|
+
</div>
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
interface ThumbIconProps {
|
|
68
|
+
readonly flipped?: boolean
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Thumb icon used in feedback buttons.
|
|
73
|
+
*
|
|
74
|
+
* @private
|
|
75
|
+
* @param props - Whether to flip the icon (for thumbs-down).
|
|
76
|
+
* @returns SVG element.
|
|
77
|
+
*/
|
|
78
|
+
function ThumbIcon(props: ThumbIconProps): React.ReactElement {
|
|
79
|
+
const transform = match(props.flipped === true)
|
|
80
|
+
.with(true, () => 'rotate(180deg)')
|
|
81
|
+
.otherwise(() => 'none')
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<svg
|
|
85
|
+
width="11"
|
|
86
|
+
height="11"
|
|
87
|
+
viewBox="0 0 24 24"
|
|
88
|
+
fill="none"
|
|
89
|
+
stroke="currentColor"
|
|
90
|
+
strokeWidth="2"
|
|
91
|
+
style={{ transform }}
|
|
92
|
+
>
|
|
93
|
+
<path d="M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.28a2 2 0 0 0 2-1.7l1.38-9a2 2 0 0 0-2-2.3zM7 22H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h3" />
|
|
94
|
+
</svg>
|
|
95
|
+
)
|
|
96
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
.cp-meta-actions {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-wrap: wrap;
|
|
4
|
+
gap: 16px;
|
|
5
|
+
font-family: var(--cp-font-family-mono);
|
|
6
|
+
font-size: 12px;
|
|
7
|
+
color: var(--cp-c-text-3);
|
|
8
|
+
margin-bottom: 24px;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.cp-meta-actions__item {
|
|
12
|
+
display: inline-flex;
|
|
13
|
+
align-items: center;
|
|
14
|
+
gap: 6px;
|
|
15
|
+
color: var(--cp-c-text-2);
|
|
16
|
+
transition: color 0.15s ease;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.cp-meta-actions__item:hover {
|
|
20
|
+
color: var(--cp-c-text-1);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.cp-meta-actions__icon {
|
|
24
|
+
display: inline-flex;
|
|
25
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { match } from 'massaman/match'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
|
|
4
|
+
import { safeUrl } from '../../lib/safe-url.ts'
|
|
5
|
+
|
|
6
|
+
import './meta-actions.css'
|
|
7
|
+
|
|
8
|
+
export interface MetaAction {
|
|
9
|
+
readonly href: string
|
|
10
|
+
readonly label: string
|
|
11
|
+
readonly icon?: React.ReactNode
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface MetaActionsProps {
|
|
15
|
+
/**
|
|
16
|
+
* List of actions to render — typically Edit on GitHub, Report issue, Discuss.
|
|
17
|
+
*/
|
|
18
|
+
readonly actions: readonly MetaAction[]
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* MetaActions — horizontal row of mono-styled links rendered below the
|
|
23
|
+
* feedback widget on doc pages. Each action is an external/internal link
|
|
24
|
+
* with an optional leading icon.
|
|
25
|
+
*
|
|
26
|
+
* @param props - Actions to render.
|
|
27
|
+
* @returns React element, or null when no actions are provided.
|
|
28
|
+
*/
|
|
29
|
+
export function MetaActions(props: MetaActionsProps): React.ReactElement | null {
|
|
30
|
+
const safeActions = props.actions.flatMap((action) => {
|
|
31
|
+
const href = safeUrl(action.href)
|
|
32
|
+
if (href === null) {
|
|
33
|
+
return []
|
|
34
|
+
}
|
|
35
|
+
return [{ ...action, href }]
|
|
36
|
+
})
|
|
37
|
+
return match(safeActions.length === 0)
|
|
38
|
+
.with(true, () => null)
|
|
39
|
+
.otherwise(() => (
|
|
40
|
+
<div className="cp-meta-actions">
|
|
41
|
+
{safeActions.map((action) => (
|
|
42
|
+
<a
|
|
43
|
+
key={`${action.label}:${action.href}`}
|
|
44
|
+
className="cp-meta-actions__item"
|
|
45
|
+
href={action.href}
|
|
46
|
+
>
|
|
47
|
+
{match(action.icon)
|
|
48
|
+
.with(undefined, () => null)
|
|
49
|
+
.otherwise((icon) => (
|
|
50
|
+
<span className="cp-meta-actions__icon">{icon}</span>
|
|
51
|
+
))}
|
|
52
|
+
<span>{action.label}</span>
|
|
53
|
+
</a>
|
|
54
|
+
))}
|
|
55
|
+
</div>
|
|
56
|
+
))
|
|
57
|
+
}
|