@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,637 @@
|
|
|
1
|
+
import { TOKEN_TO_CSS_VAR } from '@ciderpress/theme'
|
|
2
|
+
import type { ThemeColors, ThemeVariant, TokenPath } from '@ciderpress/theme'
|
|
3
|
+
import { match, P } from 'massaman/match'
|
|
4
|
+
import { useEffect, useLayoutEffect } from 'react'
|
|
5
|
+
import type React from 'react'
|
|
6
|
+
|
|
7
|
+
import { syncThemeFavicon } from '../lib/theme-favicon.ts'
|
|
8
|
+
|
|
9
|
+
declare const __CIDERPRESS_THEME_NAME__: string
|
|
10
|
+
declare const __CIDERPRESS_DEFAULT_VARIANT__: string
|
|
11
|
+
declare const __CIDERPRESS_THEME_COLORS__: string
|
|
12
|
+
declare const __CIDERPRESS_THEME_DARK_COLORS__: string
|
|
13
|
+
declare const __CIDERPRESS_THEME_REGISTRY__: string
|
|
14
|
+
|
|
15
|
+
interface RegistryEntry {
|
|
16
|
+
readonly name: string
|
|
17
|
+
readonly variants: readonly ThemeVariant[]
|
|
18
|
+
readonly defaultVariant: ThemeVariant
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* `ThemeColors` field names whose values are applied to the DOM as
|
|
23
|
+
* CSS custom-property overrides.
|
|
24
|
+
*/
|
|
25
|
+
type ThemeColorKey = keyof ThemeColors
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Parsed theme registry — built-in themes plus any user themes from
|
|
29
|
+
* `config.themes`. Read from the build-time define so the client bundle
|
|
30
|
+
* does not pull `@ciderpress/theme`'s factory + Zod into the runtime path.
|
|
31
|
+
*/
|
|
32
|
+
const REGISTRY_ENTRIES: readonly RegistryEntry[] = parseRegistry(__CIDERPRESS_THEME_REGISTRY__)
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Variants supported by each registered theme, keyed by theme name. Used
|
|
36
|
+
* to set `data-cp-variants` on `<html>` so CSS can hide the appearance
|
|
37
|
+
* toggle for themes that only declare one variant.
|
|
38
|
+
*/
|
|
39
|
+
const VARIANTS_BY_THEME: Readonly<Record<string, string>> = Object.freeze(
|
|
40
|
+
Object.fromEntries(REGISTRY_ENTRIES.map((entry) => [entry.name, entry.variants.join(' ')]))
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Default variant for each theme — used when no `ciderpress-variant` is
|
|
45
|
+
* persisted in `localStorage` or when the persisted value is stale.
|
|
46
|
+
*/
|
|
47
|
+
const DEFAULT_VARIANT_BY_THEME: Readonly<Record<string, ThemeVariant>> = Object.freeze(
|
|
48
|
+
Object.fromEntries(REGISTRY_ENTRIES.map((entry) => [entry.name, entry.defaultVariant]))
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Set of theme names known at build time. Used to validate a persisted
|
|
53
|
+
* theme name from `localStorage` before applying it.
|
|
54
|
+
*/
|
|
55
|
+
const REGISTERED_THEME_NAMES: ReadonlySet<string> = new Set(REGISTRY_ENTRIES.map((e) => e.name))
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Mapping from each `ThemeColors` field to the canonical `CiderpressTokens`
|
|
59
|
+
* leaf path that backs it. Fields whose only target is a legacy `--rp-*`
|
|
60
|
+
* var have `null` here.
|
|
61
|
+
*/
|
|
62
|
+
const THEME_COLOR_TOKEN_PATHS: Readonly<Record<ThemeColorKey, TokenPath | null>> = Object.freeze({
|
|
63
|
+
brand: 'colors.brand.primary',
|
|
64
|
+
brandLight: null,
|
|
65
|
+
brandDark: 'colors.brand.hover',
|
|
66
|
+
brandSoft: 'colors.brand.soft',
|
|
67
|
+
bg: 'colors.surface.bg',
|
|
68
|
+
bgAlt: 'colors.surface.bgAlt',
|
|
69
|
+
bgElv: 'colors.surface.bgElv',
|
|
70
|
+
bgSoft: 'colors.surface.bgSoft',
|
|
71
|
+
text1: 'colors.text.text1',
|
|
72
|
+
text2: 'colors.text.text2',
|
|
73
|
+
text3: 'colors.text.text3',
|
|
74
|
+
divider: 'colors.border.divider',
|
|
75
|
+
border: 'colors.border.border',
|
|
76
|
+
homeBg: null,
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Legacy `--rp-*` CSS variable names still emitted alongside the
|
|
81
|
+
* canonical `--cp-*` vars so Rspress's built-in styles continue to
|
|
82
|
+
* pick up theme color overrides without modification.
|
|
83
|
+
*/
|
|
84
|
+
const LEGACY_RP_VARS: Readonly<Record<ThemeColorKey, readonly string[]>> = Object.freeze({
|
|
85
|
+
brand: ['--rp-c-brand'],
|
|
86
|
+
brandLight: ['--rp-c-brand-light'],
|
|
87
|
+
brandDark: ['--rp-c-brand-dark'],
|
|
88
|
+
brandSoft: ['--rp-c-brand-tint'],
|
|
89
|
+
bg: ['--rp-c-bg'],
|
|
90
|
+
bgAlt: [],
|
|
91
|
+
bgElv: [],
|
|
92
|
+
bgSoft: ['--rp-c-bg-soft'],
|
|
93
|
+
text1: ['--rp-c-text-1'],
|
|
94
|
+
text2: ['--rp-c-text-2'],
|
|
95
|
+
text3: ['--rp-c-text-3'],
|
|
96
|
+
divider: ['--rp-c-divider'],
|
|
97
|
+
border: [],
|
|
98
|
+
homeBg: ['--rp-home-background-bg'],
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Mapping from each `ThemeColors` field name to the ordered list of CSS
|
|
103
|
+
* variables it sets on `<html>`. The canonical `--cp-*` var (resolved
|
|
104
|
+
* from `TOKEN_TO_CSS_VAR`) is emitted first, followed by any legacy
|
|
105
|
+
* `--rp-*` vars from `LEGACY_RP_VARS`.
|
|
106
|
+
*/
|
|
107
|
+
const COLOR_VAR_MAP: Readonly<Record<ThemeColorKey, readonly string[]>> = Object.freeze(
|
|
108
|
+
Object.fromEntries(
|
|
109
|
+
(Object.keys(THEME_COLOR_TOKEN_PATHS) as readonly ThemeColorKey[]).map((key) => {
|
|
110
|
+
const tokenPath = THEME_COLOR_TOKEN_PATHS[key]
|
|
111
|
+
const legacy = LEGACY_RP_VARS[key]
|
|
112
|
+
const cpVar = match(tokenPath)
|
|
113
|
+
.with(P.nullish, () => [] as readonly string[])
|
|
114
|
+
.otherwise((path) => [TOKEN_TO_CSS_VAR[path]] as readonly string[])
|
|
115
|
+
return [key, [...cpVar, ...legacy]] as const
|
|
116
|
+
})
|
|
117
|
+
) as Record<ThemeColorKey, readonly string[]>
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Collect all CSS variable names from the color map.
|
|
122
|
+
*/
|
|
123
|
+
const ALL_CSS_VARS: readonly string[] = Object.values(COLOR_VAR_MAP).flat()
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Minimum time (ms) the loading overlay stays visible before fading out.
|
|
127
|
+
*/
|
|
128
|
+
const LOADER_MIN_DISPLAY_MS = 150
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Duration (ms) of the CSS fade-out transition. Must match the
|
|
132
|
+
* `transition: opacity` value in loader-backdrop.css / loader-dots.css.
|
|
133
|
+
*/
|
|
134
|
+
const LOADER_FADE_MS = 200
|
|
135
|
+
|
|
136
|
+
const useIsomorphicLayoutEffect = getIsomorphicEffect()
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* ThemeProvider — global UI component that configures the active theme
|
|
140
|
+
* and variant.
|
|
141
|
+
*
|
|
142
|
+
* Sets `data-cp-theme`, `data-cp-variant`, and `data-cp-variants` on
|
|
143
|
+
* `<html>` so theme CSS resolves correctly. Mirrors variant changes to
|
|
144
|
+
* Rspress's `.rp-dark` class so Rspress's built-in components stay in
|
|
145
|
+
* sync with our variant state, and observes Rspress's class to persist
|
|
146
|
+
* variant changes triggered by Rspress's own dark-mode toggle.
|
|
147
|
+
*
|
|
148
|
+
* Sets `data-cp-ready` on `<html>` to dismiss the loading overlay
|
|
149
|
+
* injected by critical CSS.
|
|
150
|
+
*
|
|
151
|
+
* @returns Null element (side-effect only component)
|
|
152
|
+
*/
|
|
153
|
+
export function ThemeProvider(): React.ReactElement | null {
|
|
154
|
+
useIsomorphicLayoutEffect(() => {
|
|
155
|
+
const html = document.documentElement
|
|
156
|
+
const persistedTheme = safeGetItem('ciderpress-theme')
|
|
157
|
+
const themeName = resolveActiveThemeName(persistedTheme)
|
|
158
|
+
if (persistedTheme !== null && persistedTheme !== themeName) {
|
|
159
|
+
safeRemoveItem('ciderpress-theme')
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const persistedVariant = safeGetVariant('ciderpress-variant')
|
|
163
|
+
const buildTimeDefault = parseVariant(__CIDERPRESS_DEFAULT_VARIANT__)
|
|
164
|
+
const variant = resolveActiveVariant({
|
|
165
|
+
persisted: persistedVariant,
|
|
166
|
+
buildTimeDefault,
|
|
167
|
+
themeName,
|
|
168
|
+
})
|
|
169
|
+
if (persistedVariant !== null && persistedVariant !== variant) {
|
|
170
|
+
safeRemoveItem('ciderpress-variant')
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const colors = parseColors(__CIDERPRESS_THEME_COLORS__)
|
|
174
|
+
const darkColors = parseColors(__CIDERPRESS_THEME_DARK_COLORS__)
|
|
175
|
+
const hasColors = Object.keys(colors).length > 0
|
|
176
|
+
const hasDarkColors = Object.keys(darkColors).length > 0
|
|
177
|
+
|
|
178
|
+
html.dataset.cpTheme = themeName
|
|
179
|
+
|
|
180
|
+
// Set supported variants — CSS hides the appearance toggle for themes
|
|
181
|
+
// that only declare one variant.
|
|
182
|
+
const variants = VARIANTS_BY_THEME[themeName]
|
|
183
|
+
if (variants) {
|
|
184
|
+
html.dataset.cpVariants = variants
|
|
185
|
+
} else {
|
|
186
|
+
html.dataset.cpVariants = 'dark'
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Apply the resolved variant — both as `data-cp-variant` (our canonical
|
|
190
|
+
// attribute used by emitted theme CSS) and by mirroring to Rspress's
|
|
191
|
+
// `.rp-dark` class so its components stay aligned.
|
|
192
|
+
applyVariant(html, variant)
|
|
193
|
+
|
|
194
|
+
if (hasColors) {
|
|
195
|
+
applyColorOverrides(html, colors)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (variant === 'dark' && hasDarkColors) {
|
|
199
|
+
applyColorOverrides(html, darkColors)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Sync the document favicon with the active theme's brand colour.
|
|
203
|
+
// Browsers cache static icon assets and ignore CSS, so the only way to
|
|
204
|
+
// keep the tab mark in lockstep with the chosen theme is to swap
|
|
205
|
+
// `<link rel="icon">` to a data-URI SVG carrying the resolved
|
|
206
|
+
// `--cp-c-brand-1`.
|
|
207
|
+
syncThemeFavicon(html)
|
|
208
|
+
|
|
209
|
+
// Observe `.rp-dark` class changes so Rspress's built-in dark toggle
|
|
210
|
+
// stays the single source of truth for variant flips. The new variant
|
|
211
|
+
// must be present in the active theme's supported set; if it isn't
|
|
212
|
+
// (e.g. a devtools tinkerer flips `.rp-dark` off on `midnight`, which
|
|
213
|
+
// is dark-only), snap the class back rather than persisting an
|
|
214
|
+
// unsupported variant.
|
|
215
|
+
const supportedSet = new Set((VARIANTS_BY_THEME[themeName] ?? 'dark').split(' '))
|
|
216
|
+
const observer = new MutationObserver((mutations) => {
|
|
217
|
+
const classChanged = mutations.some((m) => m.attributeName === 'class')
|
|
218
|
+
if (!classChanged) {
|
|
219
|
+
return
|
|
220
|
+
}
|
|
221
|
+
const nowDark = html.classList.contains('rp-dark')
|
|
222
|
+
const nextVariant = match(nowDark)
|
|
223
|
+
.with(true, (): ThemeVariant => 'dark')
|
|
224
|
+
.otherwise((): ThemeVariant => 'light')
|
|
225
|
+
if (html.dataset.cpVariant === nextVariant) {
|
|
226
|
+
return
|
|
227
|
+
}
|
|
228
|
+
if (!supportedSet.has(nextVariant)) {
|
|
229
|
+
// Unsupported variant for the active theme — snap Rspress's
|
|
230
|
+
// class state back to match the current `data-cp-variant` and
|
|
231
|
+
// do NOT persist the illegal flip.
|
|
232
|
+
//
|
|
233
|
+
// The classList mutation below re-fires this observer. The
|
|
234
|
+
// re-entrant pass hits the `dataset.cpVariant === nextVariant`
|
|
235
|
+
// early return above — safe, just one extra callback.
|
|
236
|
+
if (html.dataset.cpVariant === 'dark') {
|
|
237
|
+
html.classList.add('rp-dark', 'dark')
|
|
238
|
+
html.dataset.dark = 'true'
|
|
239
|
+
} else {
|
|
240
|
+
html.classList.remove('rp-dark', 'dark')
|
|
241
|
+
html.dataset.dark = 'false'
|
|
242
|
+
}
|
|
243
|
+
return
|
|
244
|
+
}
|
|
245
|
+
html.dataset.cpVariant = nextVariant
|
|
246
|
+
persistVariant(nextVariant)
|
|
247
|
+
clearColorOverrides(html)
|
|
248
|
+
if (hasColors) {
|
|
249
|
+
applyColorOverrides(html, colors)
|
|
250
|
+
}
|
|
251
|
+
if (nextVariant === 'dark' && hasDarkColors) {
|
|
252
|
+
applyColorOverrides(html, darkColors)
|
|
253
|
+
}
|
|
254
|
+
// Re-sync the favicon — brand colour is constant across variants for
|
|
255
|
+
// built-in themes, but surface overrides on the dark variant can
|
|
256
|
+
// affect the chip background colour we bake into the SVG.
|
|
257
|
+
syncThemeFavicon(html)
|
|
258
|
+
})
|
|
259
|
+
observer.observe(html, { attributes: true, attributeFilter: ['class'] })
|
|
260
|
+
|
|
261
|
+
const cancelLoader = dismissLoader(html)
|
|
262
|
+
|
|
263
|
+
return () => {
|
|
264
|
+
observer.disconnect()
|
|
265
|
+
cancelLoader()
|
|
266
|
+
}
|
|
267
|
+
}, [])
|
|
268
|
+
|
|
269
|
+
return null
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export { ThemeProvider as default }
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Resolve the theme name to apply for this render.
|
|
276
|
+
*
|
|
277
|
+
* Returns the persisted localStorage value when it points at a registered
|
|
278
|
+
* theme; falls back to the build-time default otherwise.
|
|
279
|
+
*
|
|
280
|
+
* @private
|
|
281
|
+
* @param persisted - Value read from `localStorage['ciderpress-theme']` (or `null`)
|
|
282
|
+
* @returns Resolved theme name
|
|
283
|
+
*/
|
|
284
|
+
function resolveActiveThemeName(persisted: string | null): string {
|
|
285
|
+
if (persisted !== null && REGISTERED_THEME_NAMES.has(persisted)) {
|
|
286
|
+
return persisted
|
|
287
|
+
}
|
|
288
|
+
return __CIDERPRESS_THEME_NAME__
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Resolve the variant to apply for this render. Honours persisted state
|
|
293
|
+
* when it's still valid for the active theme; otherwise uses the theme's
|
|
294
|
+
* own default variant; otherwise the build-time default; otherwise dark.
|
|
295
|
+
*
|
|
296
|
+
* Keep the resolution ladder in sync with:
|
|
297
|
+
* - `packages/ui/src/config.ts` → `buildHeadScriptBody` (head IIFE)
|
|
298
|
+
* - `theme-switcher.tsx` → `applyTheme` (user-driven theme change)
|
|
299
|
+
*
|
|
300
|
+
* @private
|
|
301
|
+
* @param params - Persisted variant, build-time default, active theme name
|
|
302
|
+
* @returns Resolved variant for this render
|
|
303
|
+
*/
|
|
304
|
+
function resolveActiveVariant(params: {
|
|
305
|
+
readonly persisted: ThemeVariant | null
|
|
306
|
+
readonly buildTimeDefault: ThemeVariant | null
|
|
307
|
+
readonly themeName: string
|
|
308
|
+
}): ThemeVariant {
|
|
309
|
+
const supported = VARIANTS_BY_THEME[params.themeName]
|
|
310
|
+
const supportedVariants = match(supported)
|
|
311
|
+
.with(P.nullish, () => ['dark'])
|
|
312
|
+
.otherwise((s) => s.split(' '))
|
|
313
|
+
const supportedSet = new Set(supportedVariants)
|
|
314
|
+
if (params.persisted !== null && supportedSet.has(params.persisted)) {
|
|
315
|
+
return params.persisted
|
|
316
|
+
}
|
|
317
|
+
const themeDefault = DEFAULT_VARIANT_BY_THEME[params.themeName]
|
|
318
|
+
if (themeDefault !== undefined && supportedSet.has(themeDefault)) {
|
|
319
|
+
return themeDefault
|
|
320
|
+
}
|
|
321
|
+
if (params.buildTimeDefault !== null && supportedSet.has(params.buildTimeDefault)) {
|
|
322
|
+
return params.buildTimeDefault
|
|
323
|
+
}
|
|
324
|
+
if (supportedSet.has('dark')) {
|
|
325
|
+
return 'dark'
|
|
326
|
+
}
|
|
327
|
+
return 'light'
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Apply the resolved variant to `<html>` — sets `data-cp-variant`,
|
|
332
|
+
* toggles Rspress's `.rp-dark` / `.dark` classes, and mirrors the value
|
|
333
|
+
* into Rspress's `localStorage['rspress-theme-appearance']` key so a
|
|
334
|
+
* server-rendered first paint matches.
|
|
335
|
+
*
|
|
336
|
+
* @private
|
|
337
|
+
* @param html - HTML document element
|
|
338
|
+
* @param variant - Variant to apply
|
|
339
|
+
*/
|
|
340
|
+
function applyVariant(html: HTMLElement, variant: ThemeVariant): void {
|
|
341
|
+
html.dataset.cpVariant = variant
|
|
342
|
+
if (variant === 'dark') {
|
|
343
|
+
html.classList.add('rp-dark', 'dark')
|
|
344
|
+
html.dataset.dark = 'true'
|
|
345
|
+
} else {
|
|
346
|
+
html.classList.remove('rp-dark', 'dark')
|
|
347
|
+
html.dataset.dark = 'false'
|
|
348
|
+
}
|
|
349
|
+
try {
|
|
350
|
+
localStorage.setItem('rspress-theme-appearance', variant)
|
|
351
|
+
} catch {
|
|
352
|
+
// storage unavailable
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Persist the user's variant preference so it survives reloads.
|
|
358
|
+
*
|
|
359
|
+
* @private
|
|
360
|
+
* @param variant - Variant to remember
|
|
361
|
+
*/
|
|
362
|
+
function persistVariant(variant: ThemeVariant): void {
|
|
363
|
+
try {
|
|
364
|
+
localStorage.setItem('ciderpress-variant', variant)
|
|
365
|
+
} catch {
|
|
366
|
+
// storage unavailable
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Build a flat list of [cssVar, value] pairs from a color overrides object.
|
|
372
|
+
*
|
|
373
|
+
* @private
|
|
374
|
+
* @param colors - Color key-value overrides
|
|
375
|
+
* @returns Array of CSS variable/value pairs
|
|
376
|
+
*/
|
|
377
|
+
function resolveColorPairs(colors: Record<string, string>): readonly (readonly [string, string])[] {
|
|
378
|
+
return Object.entries(colors).flatMap(([key, value]) => {
|
|
379
|
+
const vars = (COLOR_VAR_MAP as Record<string, readonly string[] | undefined>)[key]
|
|
380
|
+
if (!Array.isArray(vars)) {
|
|
381
|
+
return []
|
|
382
|
+
}
|
|
383
|
+
return vars.map((cssVar) => [cssVar, value] as const)
|
|
384
|
+
})
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Apply a ThemeColors object as inline CSS custom properties on <html>.
|
|
389
|
+
*
|
|
390
|
+
* @private
|
|
391
|
+
* @param html - HTML document element
|
|
392
|
+
* @param colors - Color key-value overrides to apply
|
|
393
|
+
*/
|
|
394
|
+
function applyColorOverrides(html: HTMLElement, colors: Record<string, string>): void {
|
|
395
|
+
const pairs = resolveColorPairs(colors)
|
|
396
|
+
// oxlint-disable-next-line no-unused-expressions -- DOM side effect; for-loops and forEach also banned
|
|
397
|
+
pairs.map(([cssVar, value]) => html.style.setProperty(cssVar, value))
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Remove all color overrides previously set as inline styles.
|
|
402
|
+
*
|
|
403
|
+
* @private
|
|
404
|
+
* @param html - HTML document element
|
|
405
|
+
*/
|
|
406
|
+
function clearColorOverrides(html: HTMLElement): void {
|
|
407
|
+
// oxlint-disable-next-line no-unused-expressions -- DOM side effect; for-loops and forEach also banned
|
|
408
|
+
ALL_CSS_VARS.map((cssVar) => html.style.removeProperty(cssVar))
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Safe localStorage read — returns null if storage is unavailable.
|
|
413
|
+
*
|
|
414
|
+
* @private
|
|
415
|
+
* @param key - localStorage key to read
|
|
416
|
+
* @returns Stored value or null
|
|
417
|
+
*/
|
|
418
|
+
function safeGetItem(key: string): string | null {
|
|
419
|
+
try {
|
|
420
|
+
return localStorage.getItem(key)
|
|
421
|
+
} catch {
|
|
422
|
+
return null
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Safe localStorage delete — silently ignores storage exceptions.
|
|
428
|
+
*
|
|
429
|
+
* @private
|
|
430
|
+
* @param key - localStorage key to remove
|
|
431
|
+
*/
|
|
432
|
+
function safeRemoveItem(key: string): void {
|
|
433
|
+
try {
|
|
434
|
+
localStorage.removeItem(key)
|
|
435
|
+
} catch {
|
|
436
|
+
// storage unavailable
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Read a localStorage entry and parse it as a `ThemeVariant`. Returns
|
|
442
|
+
* `null` when the entry is missing, unparseable, or not a known variant.
|
|
443
|
+
*
|
|
444
|
+
* @private
|
|
445
|
+
* @param key - localStorage key to read
|
|
446
|
+
* @returns Parsed variant or `null`
|
|
447
|
+
*/
|
|
448
|
+
function safeGetVariant(key: string): ThemeVariant | null {
|
|
449
|
+
return parseVariant(safeGetItem(key))
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Coerce a raw string into a `ThemeVariant`, or return `null` when the
|
|
454
|
+
* value is not a known variant.
|
|
455
|
+
*
|
|
456
|
+
* @private
|
|
457
|
+
* @param raw - Candidate variant value (may be `null` or arbitrary string)
|
|
458
|
+
* @returns Validated variant or `null`
|
|
459
|
+
*/
|
|
460
|
+
function parseVariant(raw: string | null): ThemeVariant | null {
|
|
461
|
+
if (raw === 'dark' || raw === 'light') {
|
|
462
|
+
return raw
|
|
463
|
+
}
|
|
464
|
+
return null
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Parse the build-time theme registry define and return the resolved
|
|
469
|
+
* `{ name, variants, defaultVariant }` entries. Returns an empty array
|
|
470
|
+
* on any malformed input so the caller can intersect against an empty
|
|
471
|
+
* set without crashing.
|
|
472
|
+
*
|
|
473
|
+
* @private
|
|
474
|
+
* @param raw - Raw JSON string from `__CIDERPRESS_THEME_REGISTRY__`
|
|
475
|
+
* @returns Resolved registry entries
|
|
476
|
+
*/
|
|
477
|
+
function parseRegistry(raw: string): readonly RegistryEntry[] {
|
|
478
|
+
if (!raw || raw === '""' || raw === 'undefined') {
|
|
479
|
+
return []
|
|
480
|
+
}
|
|
481
|
+
try {
|
|
482
|
+
const parsed: unknown = JSON.parse(raw)
|
|
483
|
+
if (!Array.isArray(parsed)) {
|
|
484
|
+
return []
|
|
485
|
+
}
|
|
486
|
+
return parsed.flatMap(toRegistryEntry)
|
|
487
|
+
} catch {
|
|
488
|
+
return []
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* Lift a raw registry entry into a typed `RegistryEntry`, returning an
|
|
494
|
+
* empty array for malformed entries so the caller can `flatMap` cleanly.
|
|
495
|
+
*
|
|
496
|
+
* @private
|
|
497
|
+
* @param entry - Raw entry from the parsed JSON array
|
|
498
|
+
* @returns Single-element array on success, empty array on shape mismatch
|
|
499
|
+
*/
|
|
500
|
+
function toRegistryEntry(entry: unknown): readonly RegistryEntry[] {
|
|
501
|
+
if (entry === null || typeof entry !== 'object') {
|
|
502
|
+
return []
|
|
503
|
+
}
|
|
504
|
+
const { name, variants, defaultVariant } = entry as {
|
|
505
|
+
name?: unknown
|
|
506
|
+
variants?: unknown
|
|
507
|
+
defaultVariant?: unknown
|
|
508
|
+
}
|
|
509
|
+
if (typeof name !== 'string') {
|
|
510
|
+
return []
|
|
511
|
+
}
|
|
512
|
+
const resolvedVariants = parseVariantList(variants)
|
|
513
|
+
if (resolvedVariants.length === 0) {
|
|
514
|
+
return [{ name, variants: ['dark'], defaultVariant: 'dark' }]
|
|
515
|
+
}
|
|
516
|
+
const defaultVariantString = match(defaultVariant)
|
|
517
|
+
.with(P.string, (s) => s)
|
|
518
|
+
.otherwise(() => null)
|
|
519
|
+
const resolvedDefault = parseVariant(defaultVariantString)
|
|
520
|
+
const fallbackDefault = resolvedVariants[0] as ThemeVariant
|
|
521
|
+
return [
|
|
522
|
+
{
|
|
523
|
+
name,
|
|
524
|
+
variants: resolvedVariants,
|
|
525
|
+
defaultVariant: resolvedDefault ?? fallbackDefault,
|
|
526
|
+
},
|
|
527
|
+
]
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* Coerce an unknown value into a `readonly ThemeVariant[]`, dropping any
|
|
532
|
+
* entries that don't parse cleanly.
|
|
533
|
+
*
|
|
534
|
+
* @private
|
|
535
|
+
* @param raw - Candidate variant list from the registry JSON
|
|
536
|
+
* @returns Filtered list of known variants
|
|
537
|
+
*/
|
|
538
|
+
function parseVariantList(raw: unknown): readonly ThemeVariant[] {
|
|
539
|
+
if (!Array.isArray(raw)) {
|
|
540
|
+
return []
|
|
541
|
+
}
|
|
542
|
+
return raw.flatMap((v) => {
|
|
543
|
+
const candidate = match(v)
|
|
544
|
+
.with(P.string, (s) => s)
|
|
545
|
+
.otherwise(() => null)
|
|
546
|
+
const parsed = parseVariant(candidate)
|
|
547
|
+
if (parsed === null) {
|
|
548
|
+
return []
|
|
549
|
+
}
|
|
550
|
+
return [parsed]
|
|
551
|
+
})
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* Parse a JSON build-time define, returning an empty object on failure.
|
|
556
|
+
*
|
|
557
|
+
* @private
|
|
558
|
+
* @param raw - Raw JSON string from build-time define
|
|
559
|
+
* @returns Parsed color overrides object
|
|
560
|
+
*/
|
|
561
|
+
function parseColors(raw: string): Record<string, string> {
|
|
562
|
+
if (!raw || raw === '""' || raw === 'undefined') {
|
|
563
|
+
return {}
|
|
564
|
+
}
|
|
565
|
+
try {
|
|
566
|
+
const parsed: unknown = JSON.parse(raw)
|
|
567
|
+
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
568
|
+
return {}
|
|
569
|
+
}
|
|
570
|
+
return Object.fromEntries(
|
|
571
|
+
Object.entries(parsed as Record<string, unknown>).filter(
|
|
572
|
+
([, value]) => typeof value === 'string'
|
|
573
|
+
)
|
|
574
|
+
) as Record<string, string>
|
|
575
|
+
} catch {
|
|
576
|
+
return {}
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/**
|
|
581
|
+
* useLayoutEffect on the client, useEffect on the server (avoids SSR warning).
|
|
582
|
+
* Ensures DOM mutations happen synchronously before browser paint.
|
|
583
|
+
*
|
|
584
|
+
* @private
|
|
585
|
+
* @returns The appropriate effect hook for the current environment
|
|
586
|
+
*/
|
|
587
|
+
function getIsomorphicEffect(): typeof useLayoutEffect {
|
|
588
|
+
return match(globalThis.window)
|
|
589
|
+
.with(P.nonNullable, () => useLayoutEffect)
|
|
590
|
+
.otherwise(() => useEffect)
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
/**
|
|
594
|
+
* Clear the JS-driven dots animation interval set by loader-dots.js.
|
|
595
|
+
*
|
|
596
|
+
* @private
|
|
597
|
+
*/
|
|
598
|
+
function clearDotsInterval(): void {
|
|
599
|
+
const g = globalThis as Record<string, unknown>
|
|
600
|
+
const dotsInterval = g.__cpDotsInterval as ReturnType<typeof setInterval> | undefined
|
|
601
|
+
if (dotsInterval !== undefined) {
|
|
602
|
+
clearInterval(dotsInterval)
|
|
603
|
+
delete g.__cpDotsInterval
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
/**
|
|
608
|
+
* Dismiss the loading overlay with a two-phase approach:
|
|
609
|
+
* 1. Add `cp-loader-fade` class — CSS transitions opacity to 0
|
|
610
|
+
* 2. After transition completes — set `data-cp-ready` attribute — CSS hard removes (display: none)
|
|
611
|
+
*
|
|
612
|
+
* Also clears the JS-driven dots animation interval set by loader-dots.js.
|
|
613
|
+
*
|
|
614
|
+
* Returns a cleanup function that cancels pending timers.
|
|
615
|
+
*
|
|
616
|
+
* @private
|
|
617
|
+
* @param html - HTML document element
|
|
618
|
+
* @returns Cleanup function that cancels pending timers
|
|
619
|
+
*/
|
|
620
|
+
function dismissLoader(html: HTMLElement): () => void {
|
|
621
|
+
const fadeTimer = setTimeout(() => {
|
|
622
|
+
html.classList.add('cp-loader-fade')
|
|
623
|
+
}, LOADER_MIN_DISPLAY_MS)
|
|
624
|
+
|
|
625
|
+
const removeTimer = setTimeout(() => {
|
|
626
|
+
html.dataset.cpReady = 'true'
|
|
627
|
+
html.classList.remove('cp-loader-fade')
|
|
628
|
+
clearDotsInterval()
|
|
629
|
+
}, LOADER_MIN_DISPLAY_MS + LOADER_FADE_MS)
|
|
630
|
+
|
|
631
|
+
return () => {
|
|
632
|
+
clearTimeout(fadeTimer)
|
|
633
|
+
clearTimeout(removeTimer)
|
|
634
|
+
html.classList.remove('cp-loader-fade')
|
|
635
|
+
clearDotsInterval()
|
|
636
|
+
}
|
|
637
|
+
}
|