@wildo-ai/saas-website 1.1.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 +34 -0
- package/dist/esm/.builder.pid +9 -0
- package/dist/esm/astro/blog-post-bridge.d.ts +324 -0
- package/dist/esm/astro/blog-post-bridge.d.ts.map +1 -0
- package/dist/esm/astro/blog-post-bridge.js +57 -0
- package/dist/esm/astro/blog-post-bridge.js.map +1 -0
- package/dist/esm/astro/blog-post-head.renderer.d.ts +129 -0
- package/dist/esm/astro/blog-post-head.renderer.d.ts.map +1 -0
- package/dist/esm/astro/blog-post-head.renderer.js +139 -0
- package/dist/esm/astro/blog-post-head.renderer.js.map +1 -0
- package/dist/esm/astro/bridge-runtime.d.ts +224 -0
- package/dist/esm/astro/bridge-runtime.d.ts.map +1 -0
- package/dist/esm/astro/bridge-runtime.js +30 -0
- package/dist/esm/astro/bridge-runtime.js.map +1 -0
- package/dist/esm/astro/collect-expected-label-keys.d.ts +65 -0
- package/dist/esm/astro/collect-expected-label-keys.d.ts.map +1 -0
- package/dist/esm/astro/collect-expected-label-keys.js +101 -0
- package/dist/esm/astro/collect-expected-label-keys.js.map +1 -0
- package/dist/esm/astro/headers-renderer.d.ts +32 -0
- package/dist/esm/astro/headers-renderer.d.ts.map +1 -0
- package/dist/esm/astro/headers-renderer.js +64 -0
- package/dist/esm/astro/headers-renderer.js.map +1 -0
- package/dist/esm/astro/i18n-routing.helper.d.ts +91 -0
- package/dist/esm/astro/i18n-routing.helper.d.ts.map +1 -0
- package/dist/esm/astro/i18n-routing.helper.js +23 -0
- package/dist/esm/astro/i18n-routing.helper.js.map +1 -0
- package/dist/esm/astro/internal/html-escape.d.ts +31 -0
- package/dist/esm/astro/internal/html-escape.d.ts.map +1 -0
- package/dist/esm/astro/internal/html-escape.js +40 -0
- package/dist/esm/astro/internal/html-escape.js.map +1 -0
- package/dist/esm/astro/label-pack-loader.d.ts +134 -0
- package/dist/esm/astro/label-pack-loader.d.ts.map +1 -0
- package/dist/esm/astro/label-pack-loader.js +298 -0
- package/dist/esm/astro/label-pack-loader.js.map +1 -0
- package/dist/esm/astro/llms-txt-server.d.ts +28 -0
- package/dist/esm/astro/llms-txt-server.d.ts.map +1 -0
- package/dist/esm/astro/llms-txt-server.js +33 -0
- package/dist/esm/astro/llms-txt-server.js.map +1 -0
- package/dist/esm/astro/pick-labels-for-page.d.ts +63 -0
- package/dist/esm/astro/pick-labels-for-page.d.ts.map +1 -0
- package/dist/esm/astro/pick-labels-for-page.js +73 -0
- package/dist/esm/astro/pick-labels-for-page.js.map +1 -0
- package/dist/esm/astro/robots-renderer.d.ts +85 -0
- package/dist/esm/astro/robots-renderer.d.ts.map +1 -0
- package/dist/esm/astro/robots-renderer.js +157 -0
- package/dist/esm/astro/robots-renderer.js.map +1 -0
- package/dist/esm/astro/sitemap-coverage.d.ts +101 -0
- package/dist/esm/astro/sitemap-coverage.d.ts.map +1 -0
- package/dist/esm/astro/sitemap-coverage.js +67 -0
- package/dist/esm/astro/sitemap-coverage.js.map +1 -0
- package/dist/esm/astro/structured-data.renderer.d.ts +21 -0
- package/dist/esm/astro/structured-data.renderer.d.ts.map +1 -0
- package/dist/esm/astro/structured-data.renderer.js +142 -0
- package/dist/esm/astro/structured-data.renderer.js.map +1 -0
- package/dist/esm/astro/website-page-head.renderer.d.ts +93 -0
- package/dist/esm/astro/website-page-head.renderer.d.ts.map +1 -0
- package/dist/esm/astro/website-page-head.renderer.js +184 -0
- package/dist/esm/astro/website-page-head.renderer.js.map +1 -0
- package/dist/esm/astro/website-page-runtime.helper.d.ts +112 -0
- package/dist/esm/astro/website-page-runtime.helper.d.ts.map +1 -0
- package/dist/esm/astro/website-page-runtime.helper.js +60 -0
- package/dist/esm/astro/website-page-runtime.helper.js.map +1 -0
- package/dist/esm/astro/website-site-context.d.ts +126 -0
- package/dist/esm/astro/website-site-context.d.ts.map +1 -0
- package/dist/esm/astro/website-site-context.js +37 -0
- package/dist/esm/astro/website-site-context.js.map +1 -0
- package/dist/esm/astro-island.d.ts +56 -0
- package/dist/esm/astro-island.d.ts.map +1 -0
- package/dist/esm/astro-island.js +61 -0
- package/dist/esm/astro-island.js.map +1 -0
- package/dist/esm/astro.d.ts +151 -0
- package/dist/esm/astro.d.ts.map +1 -0
- package/dist/esm/astro.js +151 -0
- package/dist/esm/astro.js.map +1 -0
- package/dist/esm/companion-exports.d.ts +38 -0
- package/dist/esm/companion-exports.d.ts.map +1 -0
- package/dist/esm/companion-exports.js +38 -0
- package/dist/esm/companion-exports.js.map +1 -0
- package/dist/esm/components/low-level/WebsiteButton.d.ts +73 -0
- package/dist/esm/components/low-level/WebsiteButton.d.ts.map +1 -0
- package/dist/esm/components/low-level/WebsiteButton.js +68 -0
- package/dist/esm/components/low-level/WebsiteButton.js.map +1 -0
- package/dist/esm/components/low-level/WebsiteHeading.d.ts +78 -0
- package/dist/esm/components/low-level/WebsiteHeading.d.ts.map +1 -0
- package/dist/esm/components/low-level/WebsiteHeading.js +41 -0
- package/dist/esm/components/low-level/WebsiteHeading.js.map +1 -0
- package/dist/esm/components/low-level/WebsiteImage.d.ts +73 -0
- package/dist/esm/components/low-level/WebsiteImage.d.ts.map +1 -0
- package/dist/esm/components/low-level/WebsiteImage.js +21 -0
- package/dist/esm/components/low-level/WebsiteImage.js.map +1 -0
- package/dist/esm/components/low-level/WebsiteInternalButton.d.ts +90 -0
- package/dist/esm/components/low-level/WebsiteInternalButton.d.ts.map +1 -0
- package/dist/esm/components/low-level/WebsiteInternalButton.js +47 -0
- package/dist/esm/components/low-level/WebsiteInternalButton.js.map +1 -0
- package/dist/esm/components/low-level/WebsiteInternalLink.d.ts +99 -0
- package/dist/esm/components/low-level/WebsiteInternalLink.d.ts.map +1 -0
- package/dist/esm/components/low-level/WebsiteInternalLink.js +68 -0
- package/dist/esm/components/low-level/WebsiteInternalLink.js.map +1 -0
- package/dist/esm/components/low-level/WebsiteLink.d.ts +50 -0
- package/dist/esm/components/low-level/WebsiteLink.d.ts.map +1 -0
- package/dist/esm/components/low-level/WebsiteLink.js +22 -0
- package/dist/esm/components/low-level/WebsiteLink.js.map +1 -0
- package/dist/esm/components/low-level/WebsiteText.d.ts +57 -0
- package/dist/esm/components/low-level/WebsiteText.d.ts.map +1 -0
- package/dist/esm/components/low-level/WebsiteText.js +57 -0
- package/dist/esm/components/low-level/WebsiteText.js.map +1 -0
- package/dist/esm/config/define-website-config.d.ts +83 -0
- package/dist/esm/config/define-website-config.d.ts.map +1 -0
- package/dist/esm/config/define-website-config.js +126 -0
- package/dist/esm/config/define-website-config.js.map +1 -0
- package/dist/esm/config/index.d.ts +38 -0
- package/dist/esm/config/index.d.ts.map +1 -0
- package/dist/esm/config/index.js +38 -0
- package/dist/esm/config/index.js.map +1 -0
- package/dist/esm/config/load-website-config.d.ts +136 -0
- package/dist/esm/config/load-website-config.d.ts.map +1 -0
- package/dist/esm/config/load-website-config.js +177 -0
- package/dist/esm/config/load-website-config.js.map +1 -0
- package/dist/esm/config/wildo-website-config.schemas.d.ts +251 -0
- package/dist/esm/config/wildo-website-config.schemas.d.ts.map +1 -0
- package/dist/esm/config/wildo-website-config.schemas.js +302 -0
- package/dist/esm/config/wildo-website-config.schemas.js.map +1 -0
- package/dist/esm/config-loader.d.ts +35 -0
- package/dist/esm/config-loader.d.ts.map +1 -0
- package/dist/esm/config-loader.js +35 -0
- package/dist/esm/config-loader.js.map +1 -0
- package/dist/esm/core/anonymous-session/InboundContactForm.d.ts +38 -0
- package/dist/esm/core/anonymous-session/InboundContactForm.d.ts.map +1 -0
- package/dist/esm/core/anonymous-session/InboundContactForm.js +77 -0
- package/dist/esm/core/anonymous-session/InboundContactForm.js.map +1 -0
- package/dist/esm/core/anonymous-session/inbound-contact-form.schema.d.ts +41 -0
- package/dist/esm/core/anonymous-session/inbound-contact-form.schema.d.ts.map +1 -0
- package/dist/esm/core/anonymous-session/inbound-contact-form.schema.js +58 -0
- package/dist/esm/core/anonymous-session/inbound-contact-form.schema.js.map +1 -0
- package/dist/esm/core/anonymous-session/website-anonymous-session-client.d.ts +81 -0
- package/dist/esm/core/anonymous-session/website-anonymous-session-client.d.ts.map +1 -0
- package/dist/esm/core/anonymous-session/website-anonymous-session-client.js +177 -0
- package/dist/esm/core/anonymous-session/website-anonymous-session-client.js.map +1 -0
- package/dist/esm/core/contexts/WebsitePageContext.d.ts +40 -0
- package/dist/esm/core/contexts/WebsitePageContext.d.ts.map +1 -0
- package/dist/esm/core/contexts/WebsitePageContext.js +8 -0
- package/dist/esm/core/contexts/WebsitePageContext.js.map +1 -0
- package/dist/esm/core/contexts/WebsiteRuntimeContext.d.ts +162 -0
- package/dist/esm/core/contexts/WebsiteRuntimeContext.d.ts.map +1 -0
- package/dist/esm/core/contexts/WebsiteRuntimeContext.js +22 -0
- package/dist/esm/core/contexts/WebsiteRuntimeContext.js.map +1 -0
- package/dist/esm/core/contexts/WebsiteSectionContext.d.ts +47 -0
- package/dist/esm/core/contexts/WebsiteSectionContext.d.ts.map +1 -0
- package/dist/esm/core/contexts/WebsiteSectionContext.js +8 -0
- package/dist/esm/core/contexts/WebsiteSectionContext.js.map +1 -0
- package/dist/esm/core/contexts/useWebsitePage.d.ts +13 -0
- package/dist/esm/core/contexts/useWebsitePage.d.ts.map +1 -0
- package/dist/esm/core/contexts/useWebsitePage.js +22 -0
- package/dist/esm/core/contexts/useWebsitePage.js.map +1 -0
- package/dist/esm/core/contexts/useWebsiteRuntime.d.ts +19 -0
- package/dist/esm/core/contexts/useWebsiteRuntime.d.ts.map +1 -0
- package/dist/esm/core/contexts/useWebsiteRuntime.js +28 -0
- package/dist/esm/core/contexts/useWebsiteRuntime.js.map +1 -0
- package/dist/esm/core/contexts/useWebsiteSection.d.ts +12 -0
- package/dist/esm/core/contexts/useWebsiteSection.d.ts.map +1 -0
- package/dist/esm/core/contexts/useWebsiteSection.js +22 -0
- package/dist/esm/core/contexts/useWebsiteSection.js.map +1 -0
- package/dist/esm/core/external-providers/frontend-provider-registry.website.d.ts +17 -0
- package/dist/esm/core/external-providers/frontend-provider-registry.website.d.ts.map +1 -0
- package/dist/esm/core/external-providers/frontend-provider-registry.website.js +23 -0
- package/dist/esm/core/external-providers/frontend-provider-registry.website.js.map +1 -0
- package/dist/esm/core/factories/define-website-page-manifest.d.ts +70 -0
- package/dist/esm/core/factories/define-website-page-manifest.d.ts.map +1 -0
- package/dist/esm/core/factories/define-website-page-manifest.js +51 -0
- package/dist/esm/core/factories/define-website-page-manifest.js.map +1 -0
- package/dist/esm/core/factories/define-website-section.d.ts +101 -0
- package/dist/esm/core/factories/define-website-section.d.ts.map +1 -0
- package/dist/esm/core/factories/define-website-section.js +71 -0
- package/dist/esm/core/factories/define-website-section.js.map +1 -0
- package/dist/esm/core/hooks/useWebsiteDesignTokens.d.ts +23 -0
- package/dist/esm/core/hooks/useWebsiteDesignTokens.d.ts.map +1 -0
- package/dist/esm/core/hooks/useWebsiteDesignTokens.js +25 -0
- package/dist/esm/core/hooks/useWebsiteDesignTokens.js.map +1 -0
- package/dist/esm/core/hooks/useWebsiteLabel.d.ts +68 -0
- package/dist/esm/core/hooks/useWebsiteLabel.d.ts.map +1 -0
- package/dist/esm/core/hooks/useWebsiteLabel.js +103 -0
- package/dist/esm/core/hooks/useWebsiteLabel.js.map +1 -0
- package/dist/esm/core/layouts/WebsitePageLayout.d.ts +87 -0
- package/dist/esm/core/layouts/WebsitePageLayout.d.ts.map +1 -0
- package/dist/esm/core/layouts/WebsitePageLayout.js +170 -0
- package/dist/esm/core/layouts/WebsitePageLayout.js.map +1 -0
- package/dist/esm/core/layouts/WebsiteSection.d.ts +91 -0
- package/dist/esm/core/layouts/WebsiteSection.d.ts.map +1 -0
- package/dist/esm/core/layouts/WebsiteSection.js +26 -0
- package/dist/esm/core/layouts/WebsiteSection.js.map +1 -0
- package/dist/esm/core/routing/internal-routing.utils.d.ts +65 -0
- package/dist/esm/core/routing/internal-routing.utils.d.ts.map +1 -0
- package/dist/esm/core/routing/internal-routing.utils.js +14 -0
- package/dist/esm/core/routing/internal-routing.utils.js.map +1 -0
- package/dist/esm/index.d.ts +129 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +129 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/mdx/BlogPost.d.ts +193 -0
- package/dist/esm/mdx/BlogPost.d.ts.map +1 -0
- package/dist/esm/mdx/BlogPost.js +161 -0
- package/dist/esm/mdx/BlogPost.js.map +1 -0
- package/dist/esm/mdx/blog-post-collection.config.d.ts +139 -0
- package/dist/esm/mdx/blog-post-collection.config.d.ts.map +1 -0
- package/dist/esm/mdx/blog-post-collection.config.js +27 -0
- package/dist/esm/mdx/blog-post-collection.config.js.map +1 -0
- package/dist/esm/mdx/blog-post-frontmatter-source.d.ts +12 -0
- package/dist/esm/mdx/blog-post-frontmatter-source.d.ts.map +1 -0
- package/dist/esm/mdx/blog-post-frontmatter-source.js +13 -0
- package/dist/esm/mdx/blog-post-frontmatter-source.js.map +1 -0
- package/dist/esm/mdx/index.d.ts +22 -0
- package/dist/esm/mdx/index.d.ts.map +1 -0
- package/dist/esm/mdx/index.js +22 -0
- package/dist/esm/mdx/index.js.map +1 -0
- package/dist/esm/mdx/website-mdx-provider.d.ts +90 -0
- package/dist/esm/mdx/website-mdx-provider.d.ts.map +1 -0
- package/dist/esm/mdx/website-mdx-provider.js +8 -0
- package/dist/esm/mdx/website-mdx-provider.js.map +1 -0
- package/dist/esm/mdx.d.ts +38 -0
- package/dist/esm/mdx.d.ts.map +1 -0
- package/dist/esm/mdx.js +38 -0
- package/dist/esm/mdx.js.map +1 -0
- package/dist/esm/schemas/blog/blog-post-frontmatter.shared.schemas.d.ts +75 -0
- package/dist/esm/schemas/blog/blog-post-frontmatter.shared.schemas.d.ts.map +1 -0
- package/dist/esm/schemas/blog/blog-post-frontmatter.shared.schemas.js +85 -0
- package/dist/esm/schemas/blog/blog-post-frontmatter.shared.schemas.js.map +1 -0
- package/dist/esm/schemas/design-tokens/website-design-tokens.shared.schemas.d.ts +120 -0
- package/dist/esm/schemas/design-tokens/website-design-tokens.shared.schemas.d.ts.map +1 -0
- package/dist/esm/schemas/design-tokens/website-design-tokens.shared.schemas.js +152 -0
- package/dist/esm/schemas/design-tokens/website-design-tokens.shared.schemas.js.map +1 -0
- package/dist/esm/schemas/label-keys/website-label-key.schemas.d.ts +73 -0
- package/dist/esm/schemas/label-keys/website-label-key.schemas.d.ts.map +1 -0
- package/dist/esm/schemas/label-keys/website-label-key.schemas.js +76 -0
- package/dist/esm/schemas/label-keys/website-label-key.schemas.js.map +1 -0
- package/dist/esm/schemas/manifests/website-page-manifest.shared.schemas.d.ts +209 -0
- package/dist/esm/schemas/manifests/website-page-manifest.shared.schemas.d.ts.map +1 -0
- package/dist/esm/schemas/manifests/website-page-manifest.shared.schemas.js +218 -0
- package/dist/esm/schemas/manifests/website-page-manifest.shared.schemas.js.map +1 -0
- package/dist/esm/schemas/manifests/website-root-config.shared.schemas.d.ts +431 -0
- package/dist/esm/schemas/manifests/website-root-config.shared.schemas.d.ts.map +1 -0
- package/dist/esm/schemas/manifests/website-root-config.shared.schemas.js +438 -0
- package/dist/esm/schemas/manifests/website-root-config.shared.schemas.js.map +1 -0
- package/dist/esm/schemas/refs/page-ref.schemas.d.ts +40 -0
- package/dist/esm/schemas/refs/page-ref.schemas.d.ts.map +1 -0
- package/dist/esm/schemas/refs/page-ref.schemas.js +43 -0
- package/dist/esm/schemas/refs/page-ref.schemas.js.map +1 -0
- package/dist/esm/schemas/refs/section-ref.schemas.d.ts +41 -0
- package/dist/esm/schemas/refs/section-ref.schemas.d.ts.map +1 -0
- package/dist/esm/schemas/refs/section-ref.schemas.js +44 -0
- package/dist/esm/schemas/refs/section-ref.schemas.js.map +1 -0
- package/dist/esm/schemas/sections/website-section-category.shared.d.ts +63 -0
- package/dist/esm/schemas/sections/website-section-category.shared.d.ts.map +1 -0
- package/dist/esm/schemas/sections/website-section-category.shared.js +64 -0
- package/dist/esm/schemas/sections/website-section-category.shared.js.map +1 -0
- package/dist/esm/schemas/sections/website-section-definition.shared.schemas.d.ts +114 -0
- package/dist/esm/schemas/sections/website-section-definition.shared.schemas.d.ts.map +1 -0
- package/dist/esm/schemas/sections/website-section-definition.shared.schemas.js +69 -0
- package/dist/esm/schemas/sections/website-section-definition.shared.schemas.js.map +1 -0
- package/dist/esm/schemas/structured-data/structured-data-reconciliation.shared.d.ts +100 -0
- package/dist/esm/schemas/structured-data/structured-data-reconciliation.shared.d.ts.map +1 -0
- package/dist/esm/schemas/structured-data/structured-data-reconciliation.shared.js +355 -0
- package/dist/esm/schemas/structured-data/structured-data-reconciliation.shared.js.map +1 -0
- package/dist/esm/schemas/structured-data/website-structured-data.shared.schemas.d.ts +58 -0
- package/dist/esm/schemas/structured-data/website-structured-data.shared.schemas.d.ts.map +1 -0
- package/dist/esm/schemas/structured-data/website-structured-data.shared.schemas.js +2 -0
- package/dist/esm/schemas/structured-data/website-structured-data.shared.schemas.js.map +1 -0
- package/dist/esm/schemas/validators/label-pack.validator.d.ts +144 -0
- package/dist/esm/schemas/validators/label-pack.validator.d.ts.map +1 -0
- package/dist/esm/schemas/validators/label-pack.validator.js +100 -0
- package/dist/esm/schemas/validators/label-pack.validator.js.map +1 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -0
- package/package.json +143 -0
- package/src/__tests__/bundle-isolation.test.ts +607 -0
- package/src/astro/__tests__/blog-post-bridge.test.tsx +288 -0
- package/src/astro/__tests__/blog-post-head.renderer.test.ts +249 -0
- package/src/astro/__tests__/bridge-runtime.test.tsx +198 -0
- package/src/astro/__tests__/collect-expected-label-keys.test.ts +69 -0
- package/src/astro/__tests__/headers-renderer.test.ts +143 -0
- package/src/astro/__tests__/i18n-routing.helper.test.ts +116 -0
- package/src/astro/__tests__/label-pack-loader.test.ts +305 -0
- package/src/astro/__tests__/llms-txt-server.test.ts +127 -0
- package/src/astro/__tests__/pick-labels-for-page.test.ts +60 -0
- package/src/astro/__tests__/robots-renderer.test.ts +228 -0
- package/src/astro/__tests__/sitemap-coverage.test.ts +230 -0
- package/src/astro/__tests__/structured-data.renderer.test.ts +172 -0
- package/src/astro/__tests__/website-page-head.renderer.test.ts +424 -0
- package/src/astro/__tests__/website-page-runtime.helper.test.ts +157 -0
- package/src/astro/__tests__/website-site-context.test.ts +121 -0
- package/src/astro/blog-post-bridge.tsx +404 -0
- package/src/astro/blog-post-head.renderer.ts +293 -0
- package/src/astro/bridge-runtime.tsx +270 -0
- package/src/astro/collect-expected-label-keys.ts +113 -0
- package/src/astro/headers-renderer.ts +82 -0
- package/src/astro/i18n-routing.helper.ts +116 -0
- package/src/astro/internal/html-escape.ts +41 -0
- package/src/astro/label-pack-loader.ts +385 -0
- package/src/astro/llms-txt-server.ts +56 -0
- package/src/astro/pick-labels-for-page.ts +85 -0
- package/src/astro/robots-renderer.ts +171 -0
- package/src/astro/sitemap-coverage.ts +186 -0
- package/src/astro/structured-data.renderer.ts +171 -0
- package/src/astro/website-page-head.renderer.ts +279 -0
- package/src/astro/website-page-runtime.helper.ts +188 -0
- package/src/astro/website-site-context.ts +136 -0
- package/src/astro-island.ts +61 -0
- package/src/astro.ts +151 -0
- package/src/companion-exports.ts +38 -0
- package/src/components/low-level/WebsiteButton.tsx +118 -0
- package/src/components/low-level/WebsiteHeading.tsx +96 -0
- package/src/components/low-level/WebsiteImage.tsx +96 -0
- package/src/components/low-level/WebsiteInternalButton.tsx +172 -0
- package/src/components/low-level/WebsiteInternalLink.tsx +193 -0
- package/src/components/low-level/WebsiteLink.tsx +71 -0
- package/src/components/low-level/WebsiteText.tsx +75 -0
- package/src/components/low-level/__tests__/WebsiteInternalButton.test.tsx +169 -0
- package/src/components/low-level/__tests__/WebsiteInternalLink.test.tsx +202 -0
- package/src/components/low-level/__tests__/primitives.test.tsx +141 -0
- package/src/config/__tests__/define-website-config.test.ts +231 -0
- package/src/config/__tests__/load-website-config.test.ts +215 -0
- package/src/config/define-website-config.ts +134 -0
- package/src/config/index.ts +38 -0
- package/src/config/load-website-config.ts +229 -0
- package/src/config/wildo-website-config.schemas.ts +322 -0
- package/src/config-loader.ts +35 -0
- package/src/core/__tests__/WebsitePageLayout.test.tsx +184 -0
- package/src/core/__tests__/WebsiteSection.test.tsx +85 -0
- package/src/core/__tests__/contexts.test.tsx +133 -0
- package/src/core/__tests__/core-boundary.test.ts +85 -0
- package/src/core/__tests__/define-website-page-manifest.test.tsx +111 -0
- package/src/core/__tests__/define-website-section.test.tsx +89 -0
- package/src/core/__tests__/useWebsiteDesignTokens.test.tsx +41 -0
- package/src/core/__tests__/useWebsiteLabel.test.tsx +117 -0
- package/src/core/anonymous-session/InboundContactForm.tsx +157 -0
- package/src/core/anonymous-session/__tests__/inbound-contact-form.schema.test.ts +44 -0
- package/src/core/anonymous-session/inbound-contact-form.schema.ts +62 -0
- package/src/core/anonymous-session/website-anonymous-session-client.ts +194 -0
- package/src/core/contexts/WebsitePageContext.tsx +52 -0
- package/src/core/contexts/WebsiteRuntimeContext.tsx +175 -0
- package/src/core/contexts/WebsiteSectionContext.tsx +59 -0
- package/src/core/contexts/useWebsitePage.ts +28 -0
- package/src/core/contexts/useWebsiteRuntime.ts +34 -0
- package/src/core/contexts/useWebsiteSection.ts +28 -0
- package/src/core/external-providers/frontend-provider-registry.website.ts +62 -0
- package/src/core/factories/define-website-page-manifest.ts +102 -0
- package/src/core/factories/define-website-section.ts +128 -0
- package/src/core/hooks/useWebsiteDesignTokens.ts +26 -0
- package/src/core/hooks/useWebsiteLabel.ts +121 -0
- package/src/core/layouts/WebsitePageLayout.tsx +311 -0
- package/src/core/layouts/WebsiteSection.tsx +139 -0
- package/src/core/routing/__tests__/internal-routing.utils.test.ts +134 -0
- package/src/core/routing/internal-routing.utils.ts +77 -0
- package/src/index.ts +132 -0
- package/src/mdx/BlogPost.tsx +296 -0
- package/src/mdx/__tests__/BlogPost.test.tsx +218 -0
- package/src/mdx/__tests__/blog-post-collection.config.test.ts +127 -0
- package/src/mdx/__tests__/website-mdx-provider.test.tsx +82 -0
- package/src/mdx/blog-post-collection.config.ts +177 -0
- package/src/mdx/blog-post-frontmatter-source.ts +31 -0
- package/src/mdx/index.ts +39 -0
- package/src/mdx/website-mdx-provider.tsx +95 -0
- package/src/mdx.ts +38 -0
- package/src/schemas/__tests__/blog-frontmatter.test.ts +50 -0
- package/src/schemas/__tests__/companion-exports-react-free.test.ts +51 -0
- package/src/schemas/__tests__/design-tokens.test.ts +163 -0
- package/src/schemas/__tests__/label-key.test.ts +46 -0
- package/src/schemas/__tests__/label-pack-validator.test.ts +146 -0
- package/src/schemas/__tests__/page-manifest.test.ts +66 -0
- package/src/schemas/__tests__/refs.test.ts +37 -0
- package/src/schemas/__tests__/root-config.test.ts +292 -0
- package/src/schemas/__tests__/section-definition.test.ts +47 -0
- package/src/schemas/__tests__/structured-data-reconciliation.test.ts +543 -0
- package/src/schemas/blog/blog-post-frontmatter.shared.schemas.ts +111 -0
- package/src/schemas/design-tokens/website-design-tokens.shared.schemas.ts +166 -0
- package/src/schemas/label-keys/website-label-key.schemas.ts +88 -0
- package/src/schemas/manifests/website-page-manifest.shared.schemas.ts +266 -0
- package/src/schemas/manifests/website-root-config.shared.schemas.ts +504 -0
- package/src/schemas/refs/page-ref.schemas.ts +49 -0
- package/src/schemas/refs/section-ref.schemas.ts +50 -0
- package/src/schemas/sections/website-section-category.shared.ts +65 -0
- package/src/schemas/sections/website-section-definition.shared.schemas.ts +120 -0
- package/src/schemas/structured-data/structured-data-reconciliation.shared.ts +579 -0
- package/src/schemas/structured-data/website-structured-data.shared.schemas.ts +62 -0
- package/src/schemas/validators/label-pack.validator.ts +201 -0
package/src/astro.ts
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @wildo-package @wildo-ai/saas-website (astro adapter SSR/frontmatter entry)
|
|
3
|
+
*
|
|
4
|
+
* Astro **frontmatter / SSR** sub-entry. Re-exports the TypeScript
|
|
5
|
+
* helpers a consumer's per-page `.astro` files consume INSIDE
|
|
6
|
+
* frontmatter to render a marketing-site page (locale resolution,
|
|
7
|
+
* label-pack loading, `<head>` HTML rendering, design-token CSS
|
|
8
|
+
* rendering, i18n routing config, expected-label-key collection).
|
|
9
|
+
*
|
|
10
|
+
* **The `createWebsitePageBridgeIsland` factory lives in a SEPARATE
|
|
11
|
+
* subpath — `@wildo-ai/saas-website/astro-island`** — and is NOT
|
|
12
|
+
* re-exported here. The split exists because the consumer's per-page
|
|
13
|
+
* `*.bridge.tsx` module is bundled into BOTH the SSR graph (via the
|
|
14
|
+
* `.astro` page that statically imports it) AND the client graph
|
|
15
|
+
* (Astro's hydration pipeline). Re-exporting the bridge factory
|
|
16
|
+
* alongside the Node-using helpers here would force Vite to resolve
|
|
17
|
+
* `node:fs` / `node:path` for the client bundle even though
|
|
18
|
+
* tree-shaking would later drop them — resolution happens BEFORE
|
|
19
|
+
* tree-shaking, so the build hard-fails with
|
|
20
|
+
* `"resolve" is not exported by "__vite-browser-external"`. The
|
|
21
|
+
* dedicated `astro-island` subpath keeps the client import graph
|
|
22
|
+
* 100% bundle-clean.
|
|
23
|
+
*
|
|
24
|
+
* **No `.astro` shell shipped.** The Phase-5 redesign deliberately
|
|
25
|
+
* removed the previously-planned `WebsitePageBridge.astro` shell
|
|
26
|
+
* because:
|
|
27
|
+
* 1. A `.astro` file cannot be re-exported from a TS subpath barrel
|
|
28
|
+
* and `tsc` does not copy non-TS files to `dist/`, so the shell
|
|
29
|
+
* was unreachable from `@wildo-ai/saas-website/astro`.
|
|
30
|
+
* 2. The shell would have had to pass `pageComponent` /
|
|
31
|
+
* `headerComponent` / `footerComponent` (`ComponentType` refs)
|
|
32
|
+
* as React-island props — Astro's `devalue`-style island prop
|
|
33
|
+
* serialization cannot transport function values, so hydration
|
|
34
|
+
* would have mismatched on first interaction.
|
|
35
|
+
* Both failure modes are sidestepped by giving consumers two
|
|
36
|
+
* primitive-string-only TS helpers (`renderWebsitePageHeadHtml`,
|
|
37
|
+
* `renderWebsiteDesignTokensCss`) for the Astro-side `<head>` work
|
|
38
|
+
* and a closure-based island factory (`createWebsitePageBridgeIsland`,
|
|
39
|
+
* exported from `@wildo-ai/saas-website/astro-island`) for the
|
|
40
|
+
* React-island side. The consumer authors a thin per-page `.astro`
|
|
41
|
+
* file that statically imports the per-page bridge island — Astro's
|
|
42
|
+
* compiler then resolves the `client:*` directive against a static
|
|
43
|
+
* module export, which is the only pattern that makes Astro's
|
|
44
|
+
* hydration bundling work correctly. See `bridge-runtime.tsx`'s
|
|
45
|
+
* file-level JSDoc for the full rationale.
|
|
46
|
+
*
|
|
47
|
+
* @wildo-boundary
|
|
48
|
+
* This entry is on the SSR / Astro-build-time side of the bundle
|
|
49
|
+
* boundary (D12 second loosening). It MAY import:
|
|
50
|
+
* - `node:fs`, `node:path` (label-pack-loader, runtime-helper)
|
|
51
|
+
* It MUST NOT import:
|
|
52
|
+
* - the bridge-runtime module — that module ships into the
|
|
53
|
+
* client island bundle and is exposed via the
|
|
54
|
+
* `@wildo-ai/saas-website/astro-island` subpath instead.
|
|
55
|
+
* - the Astro runtime (`astro` package, `astro:*` virtual modules)
|
|
56
|
+
* — those live exclusively inside `.astro` files. Adding
|
|
57
|
+
* `astro` here would make the engine package unimportable
|
|
58
|
+
* from any context that does not boot the Astro compiler
|
|
59
|
+
* (Vitest tests, the `wildo website audit` CLI, companion
|
|
60
|
+
* subprocesses).
|
|
61
|
+
*
|
|
62
|
+
* Enforced by `engine/saas-website/src/__tests__/bundle-isolation.test.ts`
|
|
63
|
+
* which walks `astro.ts` as one of `ENTRY_FILES` AND runs a targeted
|
|
64
|
+
* negative test that `node:fs` / `node:path` are reachable from
|
|
65
|
+
* `astro.ts` ONLY — never from `index.ts`, `companion-exports.ts`,
|
|
66
|
+
* or `astro-island.ts`.
|
|
67
|
+
*/
|
|
68
|
+
|
|
69
|
+
export * from './astro/website-site-context';
|
|
70
|
+
export * from './astro/i18n-routing.helper';
|
|
71
|
+
export * from './astro/collect-expected-label-keys';
|
|
72
|
+
export * from './astro/label-pack-loader';
|
|
73
|
+
/**
|
|
74
|
+
* `pick-labels-for-page` is INTENTIONALLY NOT re-exported here.
|
|
75
|
+
* It is an internal helper consumed only by `loadWebsitePageRuntime`
|
|
76
|
+
* (which IS exported via the runtime helper below). Exposing it as a
|
|
77
|
+
* public sub-entry surface would (a) invite consumers to call it
|
|
78
|
+
* directly from `.astro` frontmatter — which they should never do
|
|
79
|
+
* (the runtime helper already wires the slicing transparently), and
|
|
80
|
+
* (b) lock the engine into an extra public API the audit step would
|
|
81
|
+
* have to keep stable. Tests import the helper via its relative
|
|
82
|
+
* path, which keeps the behavior covered without leaking the surface.
|
|
83
|
+
* (Phase 6 audit fix M-B.)
|
|
84
|
+
*/
|
|
85
|
+
export * from './astro/website-page-runtime.helper';
|
|
86
|
+
export * from './astro/website-page-head.renderer';
|
|
87
|
+
export * from './astro/structured-data.renderer';
|
|
88
|
+
/**
|
|
89
|
+
* Phase 7 — sibling head renderer for blog posts. Reads from a
|
|
90
|
+
* validated `BlogPostFrontmatter` instead of the page manifest's
|
|
91
|
+
* `metaLabelKeys`; emits `og:type=article` plus the
|
|
92
|
+
* `article:published_time` / `article:author` / `article:tag` lines
|
|
93
|
+
* the OG "article" vertical requires. See the renderer file's
|
|
94
|
+
* preamble for why this is a sibling rather than a flag on
|
|
95
|
+
* `renderWebsitePageHeadHtml`.
|
|
96
|
+
*/
|
|
97
|
+
export * from './astro/blog-post-head.renderer';
|
|
98
|
+
/**
|
|
99
|
+
* Phase 8 close-out — `renderRobotsTxt(siteContext)` pure string
|
|
100
|
+
* builder for the marketing site's `robots.txt` body. Sibling of
|
|
101
|
+
* `renderWebsitePageHeadHtml` (same Astro-frontmatter consumption
|
|
102
|
+
* shape: pure TypeScript, no Astro runtime dependency, vitest-
|
|
103
|
+
* friendly). See `engine/saas-website/src/astro/robots-renderer.ts`
|
|
104
|
+
* for the field-by-field rationale and
|
|
105
|
+
* `.cursor/plans/saas-website-seo-sitemap.md` for the slice context.
|
|
106
|
+
*/
|
|
107
|
+
export * from './astro/robots-renderer';
|
|
108
|
+
/**
|
|
109
|
+
* Phase 8 close-out — `enumerateMarketingPageSitemapUrls(siteContext)`
|
|
110
|
+
* pure helper that returns every (page-manifest × locale) absolute
|
|
111
|
+
* URL the marketing site's sitemap MUST advertise, paired with its
|
|
112
|
+
* hreflang alternates. Two consumption surfaces:
|
|
113
|
+
*
|
|
114
|
+
* 1. Vitest enforcement — a regression that drops a manifest-
|
|
115
|
+
* registered route fails fast in unit tests, well before
|
|
116
|
+
* `astro build` would surface it.
|
|
117
|
+
* 2. `@astrojs/sitemap` `customPages` integration — when the
|
|
118
|
+
* consumer needs to surface routes Astro's file-based router
|
|
119
|
+
* cannot auto-discover (data-driven landing variants, etc.),
|
|
120
|
+
* this helper computes the canonical URL list to spread into
|
|
121
|
+
* the plugin config.
|
|
122
|
+
*
|
|
123
|
+
* See `engine/saas-website/src/astro/sitemap-coverage.ts` and
|
|
124
|
+
* `.cursor/plans/saas-website-seo-sitemap.md` for the full
|
|
125
|
+
* rationale + scope boundary.
|
|
126
|
+
*/
|
|
127
|
+
export * from './astro/sitemap-coverage';
|
|
128
|
+
/**
|
|
129
|
+
* AEO — `renderPageStructuredData(opts)` pure string builder that
|
|
130
|
+
* collects every section's `getStructuredData()` (when present),
|
|
131
|
+
* dedupes by `@id`, and emits one
|
|
132
|
+
* `<script type="application/ld+json">` block per surviving payload.
|
|
133
|
+
* Sibling of `renderWebsitePageHeadHtml` (same pure-TS, vitest-
|
|
134
|
+
* friendly contract). See
|
|
135
|
+
* `engine/saas-website/src/astro/structured-data.renderer.ts`
|
|
136
|
+
* for the safety + dedupe semantics and
|
|
137
|
+
* `.cursor/plans/saas-website-aeo-geo.md` for the slice context.
|
|
138
|
+
*/
|
|
139
|
+
export * from './astro/structured-data.renderer';
|
|
140
|
+
export * from './astro/headers-renderer';
|
|
141
|
+
export * from './astro/llms-txt-server';
|
|
142
|
+
/**
|
|
143
|
+
* AEO — runtime payload type (`WebsiteStructuredDataPayload`) and
|
|
144
|
+
* convenience return-shape union (`WebsiteStructuredDataReturn`)
|
|
145
|
+
* the section author imports to type their `getStructuredData()`.
|
|
146
|
+
* Defined locally in `saas-website` (not in `saas-specifications`)
|
|
147
|
+
* because the runtime engine cannot depend on the spec layer; the
|
|
148
|
+
* companion catalog at `_catalogs/schema-org/` describes the same
|
|
149
|
+
* vocabulary at the COMPANION layer.
|
|
150
|
+
*/
|
|
151
|
+
export * from './schemas/structured-data/website-structured-data.shared.schemas';
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @wildo-package @wildo-ai/saas-website (companion entry)
|
|
3
|
+
*
|
|
4
|
+
* Narrow, React-free entry point for non-React consumers:
|
|
5
|
+
* - the `wildo-cli`'s page/sitemap/structured-data audit commands
|
|
6
|
+
* - companion subprocesses (e.g. introspection of authored pages)
|
|
7
|
+
* - CI scripts that validate label-pack coverage
|
|
8
|
+
* - anything Astro-build-time that cannot pull React into the
|
|
9
|
+
* evaluation graph
|
|
10
|
+
*
|
|
11
|
+
* Per `saas-website-slice.md` §"Note on `companion-exports.ts` scope
|
|
12
|
+
* expansion": the SEO builder library lives here too (it's pure
|
|
13
|
+
* string + JSON-LD generation, React-free by design).
|
|
14
|
+
*
|
|
15
|
+
* @wildo-boundary
|
|
16
|
+
* This file MUST re-export ONLY from `schemas/**` and `seo/**`.
|
|
17
|
+
* It MUST NOT import or re-export anything from:
|
|
18
|
+
* - `core/**` (React runtime contexts/hooks)
|
|
19
|
+
* - `astro/**` (Astro-only adapter)
|
|
20
|
+
* - `mdx/**` (MDX-only adapter)
|
|
21
|
+
* - `integration/**` (React-bearing integration code)
|
|
22
|
+
* It MUST NOT import React, Astro, or `@mdx-js/*`.
|
|
23
|
+
*
|
|
24
|
+
* Enforced by review until Phase 8 boundary lint design (see plan).
|
|
25
|
+
*
|
|
26
|
+
* Phase 1 status: schemas exposed; SEO builders land in Phase 2.5.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
export * from './schemas/refs/page-ref.schemas';
|
|
30
|
+
export * from './schemas/refs/section-ref.schemas';
|
|
31
|
+
export * from './schemas/label-keys/website-label-key.schemas';
|
|
32
|
+
export * from './schemas/sections/website-section-category.shared';
|
|
33
|
+
export * from './schemas/sections/website-section-definition.shared.schemas';
|
|
34
|
+
export * from './schemas/design-tokens/website-design-tokens.shared.schemas';
|
|
35
|
+
export * from './schemas/manifests/website-page-manifest.shared.schemas';
|
|
36
|
+
export * from './schemas/manifests/website-root-config.shared.schemas';
|
|
37
|
+
export * from './schemas/blog/blog-post-frontmatter.shared.schemas';
|
|
38
|
+
export * from './schemas/validators/label-pack.validator';
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import React, { type CSSProperties, type ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
import { WebsiteLink } from './WebsiteLink';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @wildo_source:part:start saas.website.primitives.button facet:layer:components facet:family:website
|
|
7
|
+
*
|
|
8
|
+
* `WebsiteButton` — L1 marketing-site CTA / button primitive.
|
|
9
|
+
*
|
|
10
|
+
* **Two render modes** (selected by the presence of `href`):
|
|
11
|
+
* - `href` supplied => renders as a `<WebsiteLink>` (anchor)
|
|
12
|
+
* with button styling. This is the dominant marketing-site
|
|
13
|
+
* case (CTAs link to `/signup`, `/contact`, `#pricing`).
|
|
14
|
+
* - `href` omitted => renders a real `<button type="…">` for
|
|
15
|
+
* interactive sections (eg. a "Subscribe" form button on the
|
|
16
|
+
* contact page that opens an inbound-contact dialog).
|
|
17
|
+
*
|
|
18
|
+
* **Why a single primitive** (vs. separate `<WebsiteCta>` +
|
|
19
|
+
* `<WebsiteButton>`): the visual identity is the same, and the
|
|
20
|
+
* rendered tag is a low-level concern decided per call site. A
|
|
21
|
+
* single primitive with two modes keeps the section-author API
|
|
22
|
+
* narrow (one import, one prop set) while preserving correct
|
|
23
|
+
* semantic HTML at each call site.
|
|
24
|
+
*
|
|
25
|
+
* **Variant vocabulary**: kept minimal at the placeholder level
|
|
26
|
+
* (`primary` / `secondary`). The override implementation typically
|
|
27
|
+
* extends with `tertiary`, `ghost`, `destructive`, etc. — adding
|
|
28
|
+
* variants here would lock the placeholder vocabulary against the
|
|
29
|
+
* override's richer set.
|
|
30
|
+
*/
|
|
31
|
+
export enum WebsiteButtonVariant {
|
|
32
|
+
PRIMARY = 'primary',
|
|
33
|
+
SECONDARY = 'secondary',
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export enum WebsiteButtonSize {
|
|
37
|
+
SM = 'sm',
|
|
38
|
+
MD = 'md',
|
|
39
|
+
LG = 'lg',
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
interface WebsiteButtonCommonProps {
|
|
43
|
+
variant?: WebsiteButtonVariant;
|
|
44
|
+
size?: WebsiteButtonSize;
|
|
45
|
+
className?: string;
|
|
46
|
+
style?: CSSProperties;
|
|
47
|
+
id?: string;
|
|
48
|
+
ariaLabel?: string;
|
|
49
|
+
children: ReactNode;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
interface WebsiteButtonAsLinkProps extends WebsiteButtonCommonProps {
|
|
53
|
+
href: string;
|
|
54
|
+
external?: boolean;
|
|
55
|
+
type?: never;
|
|
56
|
+
onClick?: never;
|
|
57
|
+
disabled?: never;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
interface WebsiteButtonAsButtonProps extends WebsiteButtonCommonProps {
|
|
61
|
+
href?: never;
|
|
62
|
+
external?: never;
|
|
63
|
+
type?: 'button' | 'submit' | 'reset';
|
|
64
|
+
onClick?: () => void;
|
|
65
|
+
disabled?: boolean;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Discriminated union of "anchor-shaped" vs. "button-shaped" props.
|
|
70
|
+
* Either supply `href` (renders an anchor with link semantics) OR
|
|
71
|
+
* supply `type` / `onClick` / `disabled` (renders a real button) —
|
|
72
|
+
* mixing the two is rejected at compile time.
|
|
73
|
+
*/
|
|
74
|
+
export type WebsiteButtonProps = WebsiteButtonAsLinkProps | WebsiteButtonAsButtonProps;
|
|
75
|
+
|
|
76
|
+
export function WebsiteButton(props: WebsiteButtonProps): ReactNode {
|
|
77
|
+
const variant = props.variant ?? WebsiteButtonVariant.PRIMARY;
|
|
78
|
+
const size = props.size ?? WebsiteButtonSize.MD;
|
|
79
|
+
|
|
80
|
+
if ('href' in props && props.href !== undefined) {
|
|
81
|
+
return (
|
|
82
|
+
<WebsiteLink
|
|
83
|
+
href={props.href}
|
|
84
|
+
external={props.external}
|
|
85
|
+
className={props.className}
|
|
86
|
+
style={props.style}
|
|
87
|
+
id={props.id}
|
|
88
|
+
ariaLabel={props.ariaLabel}
|
|
89
|
+
>
|
|
90
|
+
<span
|
|
91
|
+
data-website-primitive="button"
|
|
92
|
+
data-website-button-variant={variant}
|
|
93
|
+
data-website-button-size={size}
|
|
94
|
+
data-website-button-mode="link"
|
|
95
|
+
>
|
|
96
|
+
{props.children}
|
|
97
|
+
</span>
|
|
98
|
+
</WebsiteLink>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const attrs: Record<string, unknown> = {
|
|
103
|
+
'data-website-primitive': 'button',
|
|
104
|
+
'data-website-button-variant': variant,
|
|
105
|
+
'data-website-button-size': size,
|
|
106
|
+
'data-website-button-mode': 'button',
|
|
107
|
+
type: props.type ?? 'button',
|
|
108
|
+
};
|
|
109
|
+
if (props.className !== undefined) attrs.className = props.className;
|
|
110
|
+
if (props.style !== undefined) attrs.style = props.style;
|
|
111
|
+
if (props.id !== undefined) attrs.id = props.id;
|
|
112
|
+
if (props.ariaLabel !== undefined) attrs['aria-label'] = props.ariaLabel;
|
|
113
|
+
if (props.disabled !== undefined) attrs.disabled = props.disabled;
|
|
114
|
+
if (props.onClick !== undefined) attrs.onClick = props.onClick;
|
|
115
|
+
return <button {...attrs}>{props.children}</button>;
|
|
116
|
+
}
|
|
117
|
+
WebsiteButton.displayName = 'WebsiteButton';
|
|
118
|
+
/** @wildo_source:part:end saas.website.primitives.button */
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import React, { type CSSProperties, type ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @wildo_source:part:start saas.website.primitives.heading facet:layer:components facet:family:website
|
|
5
|
+
*
|
|
6
|
+
* `WebsiteHeading` — L1 marketing-site heading primitive.
|
|
7
|
+
*
|
|
8
|
+
* **Status (Phase 2 / D18 — option D)**: minimal, self-contained
|
|
9
|
+
* placeholder. The framework defaults are intentionally bare so
|
|
10
|
+
* consumers MUST override at the L1 boundary by barrel-re-exporting
|
|
11
|
+
* a styled variant from their app's
|
|
12
|
+
* `app/website/primitives/WebsiteHeading.tsx`. The Phase 6
|
|
13
|
+
* dogfood will validate the override workflow and (deferred to
|
|
14
|
+
* Phase 6) decide whether to promote these to the
|
|
15
|
+
* `ComponentRegistryService` registry shared with `saas-frontend-lib`.
|
|
16
|
+
*
|
|
17
|
+
* **Why these primitives ship at all** (vs. asking consumers to
|
|
18
|
+
* author every primitive themselves): consistency of the section
|
|
19
|
+
* authoring surface. Section authors call `<WebsiteHeading
|
|
20
|
+
* level={1}>` from `defineWebsiteSection`'d components without
|
|
21
|
+
* having to know which app they're rendering in — the override
|
|
22
|
+
* happens at the consumer barrel level transparently.
|
|
23
|
+
*
|
|
24
|
+
* **Why `level` is a discrete prop (1–6)**: heading hierarchy is
|
|
25
|
+
* critical for SEO and accessibility, and a free-string `as` prop
|
|
26
|
+
* makes it trivial for a copy-paste section to render `<h2>`s where
|
|
27
|
+
* the page outline expects `<h1>`. Constraining `level` to a numeric
|
|
28
|
+
* union keeps the section author honest about the document outline.
|
|
29
|
+
*
|
|
30
|
+
* **Why `variant` (display vs. headline) is shipped here**:
|
|
31
|
+
* marketing-site headings need a visual scale that's distinct from
|
|
32
|
+
* the in-app heading scale (display sizes for hero, headline sizes
|
|
33
|
+
* for section titles). Even as a placeholder, the variant slot
|
|
34
|
+
* exists so section authors don't have to invent the prop ad-hoc;
|
|
35
|
+
* the override implementation can deliver the actual visual scale.
|
|
36
|
+
*/
|
|
37
|
+
export type WebsiteHeadingLevel = 1 | 2 | 3 | 4 | 5 | 6;
|
|
38
|
+
|
|
39
|
+
export enum WebsiteHeadingVariant {
|
|
40
|
+
/**
|
|
41
|
+
* Hero / above-the-fold sizes. Maps to the largest type scale in
|
|
42
|
+
* the consumer's design system. Use for one-per-page copy.
|
|
43
|
+
*/
|
|
44
|
+
DISPLAY = 'display',
|
|
45
|
+
/**
|
|
46
|
+
* Section / sub-section titles. The default — most marketing-site
|
|
47
|
+
* headings are headline-scale, not display-scale.
|
|
48
|
+
*/
|
|
49
|
+
HEADLINE = 'headline',
|
|
50
|
+
/**
|
|
51
|
+
* In-section group titles (eg. pricing tier names, FAQ question
|
|
52
|
+
* cards). Smaller than headline, structurally a heading.
|
|
53
|
+
*/
|
|
54
|
+
TITLE = 'title',
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface WebsiteHeadingProps {
|
|
58
|
+
/** Required heading level (1–6). Drives the rendered tag. */
|
|
59
|
+
level: WebsiteHeadingLevel;
|
|
60
|
+
/** Visual variant. Defaults to `HEADLINE` (the most common case). */
|
|
61
|
+
variant?: WebsiteHeadingVariant;
|
|
62
|
+
/** Forwarded to the heading element. */
|
|
63
|
+
className?: string;
|
|
64
|
+
/** Forwarded to the heading element for one-off inline tweaks. */
|
|
65
|
+
style?: CSSProperties;
|
|
66
|
+
/** Forwarded id (for anchor links to specific headings). */
|
|
67
|
+
id?: string;
|
|
68
|
+
children: ReactNode;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Minimal placeholder. Renders the correct semantic tag with no
|
|
73
|
+
* visual styling beyond a `data-website-primitive="heading"`
|
|
74
|
+
* attribute that the consumer's tailwind config / global CSS / the
|
|
75
|
+
* override implementation can hook into.
|
|
76
|
+
*/
|
|
77
|
+
export function WebsiteHeading({
|
|
78
|
+
level,
|
|
79
|
+
variant = WebsiteHeadingVariant.HEADLINE,
|
|
80
|
+
className,
|
|
81
|
+
style,
|
|
82
|
+
id,
|
|
83
|
+
children,
|
|
84
|
+
}: WebsiteHeadingProps): ReactNode {
|
|
85
|
+
const Tag = `h${level}` as 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
|
|
86
|
+
const attrs: Record<string, unknown> = {
|
|
87
|
+
'data-website-primitive': 'heading',
|
|
88
|
+
'data-website-heading-variant': variant,
|
|
89
|
+
};
|
|
90
|
+
if (className !== undefined) attrs.className = className;
|
|
91
|
+
if (style !== undefined) attrs.style = style;
|
|
92
|
+
if (id !== undefined) attrs.id = id;
|
|
93
|
+
return <Tag {...attrs}>{children}</Tag>;
|
|
94
|
+
}
|
|
95
|
+
WebsiteHeading.displayName = 'WebsiteHeading';
|
|
96
|
+
/** @wildo_source:part:end saas.website.primitives.heading */
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import React, { type CSSProperties, type ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @wildo_source:part:start saas.website.primitives.image facet:layer:components facet:family:website
|
|
5
|
+
*
|
|
6
|
+
* `WebsiteImage` — L1 marketing-site image primitive.
|
|
7
|
+
*
|
|
8
|
+
* **Status (Phase 2 / D18)**: minimal placeholder around a plain
|
|
9
|
+
* `<img>` with sensible accessibility / performance defaults
|
|
10
|
+
* (`alt` is REQUIRED, `loading` defaults to `'lazy'`, `decoding`
|
|
11
|
+
* defaults to `'async'`). Production sites override at the L1
|
|
12
|
+
* boundary to wire in Astro's `<Image>` component (or a Next/CDN
|
|
13
|
+
* variant) for automatic format / width / DPR handling.
|
|
14
|
+
*
|
|
15
|
+
* **Why `alt` is REQUIRED at the type level**: marketing imagery
|
|
16
|
+
* is a public-facing surface where missing alt text directly hurts
|
|
17
|
+
* SEO scoring AND blocks screen-reader access. Forcing the section
|
|
18
|
+
* author to author an `alt` (even an empty `''` for purely
|
|
19
|
+
* decorative images) makes the accessibility decision explicit at
|
|
20
|
+
* the call site.
|
|
21
|
+
*
|
|
22
|
+
* **Why `width` / `height` are REQUIRED**: cumulative-layout-shift
|
|
23
|
+
* is a Core Web Vital that marketing pages are routinely scored
|
|
24
|
+
* against. Without intrinsic dimensions, every image on the page
|
|
25
|
+
* triggers layout shift on load. Forcing the dimensions at the call
|
|
26
|
+
* site keeps CLS near zero by default — the override implementation
|
|
27
|
+
* can swap to Astro's `<Image>` (which encodes the same dimensions
|
|
28
|
+
* differently) without changing the section-author API.
|
|
29
|
+
*
|
|
30
|
+
* **Why no `priority` / `fetchpriority` prop here**: Astro's
|
|
31
|
+
* `<Image>` exposes its own `loading="eager"` / `priority` props
|
|
32
|
+
* with implementation-specific semantics, and a placeholder-level
|
|
33
|
+
* `priority` would be either a lie (does nothing without the Astro
|
|
34
|
+
* adapter) or a footgun (forces the override to honor a contract
|
|
35
|
+
* it can't always meet). The override implementation is the right
|
|
36
|
+
* place to expose this.
|
|
37
|
+
*/
|
|
38
|
+
export interface WebsiteImageProps {
|
|
39
|
+
src: string;
|
|
40
|
+
/**
|
|
41
|
+
* REQUIRED. Use `alt=""` (empty string) for purely decorative
|
|
42
|
+
* images — `null` / `undefined` is rejected at compile time so
|
|
43
|
+
* the accessibility decision is always explicit.
|
|
44
|
+
*/
|
|
45
|
+
alt: string;
|
|
46
|
+
/**
|
|
47
|
+
* REQUIRED. Intrinsic image width in CSS pixels (eg. `1200`).
|
|
48
|
+
*/
|
|
49
|
+
width: number;
|
|
50
|
+
/**
|
|
51
|
+
* REQUIRED. Intrinsic image height in CSS pixels (eg. `630`).
|
|
52
|
+
*/
|
|
53
|
+
height: number;
|
|
54
|
+
/**
|
|
55
|
+
* `'lazy'` (default) defers loading until the image is near the
|
|
56
|
+
* viewport. Set to `'eager'` for above-the-fold hero images.
|
|
57
|
+
*/
|
|
58
|
+
loading?: 'lazy' | 'eager';
|
|
59
|
+
/**
|
|
60
|
+
* `'async'` (default) lets the browser decode the image off the
|
|
61
|
+
* main thread. Set to `'sync'` only for above-the-fold hero
|
|
62
|
+
* images that need to paint immediately.
|
|
63
|
+
*/
|
|
64
|
+
decoding?: 'async' | 'sync' | 'auto';
|
|
65
|
+
className?: string;
|
|
66
|
+
style?: CSSProperties;
|
|
67
|
+
id?: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function WebsiteImage({
|
|
71
|
+
src,
|
|
72
|
+
alt,
|
|
73
|
+
width,
|
|
74
|
+
height,
|
|
75
|
+
loading = 'lazy',
|
|
76
|
+
decoding = 'async',
|
|
77
|
+
className,
|
|
78
|
+
style,
|
|
79
|
+
id,
|
|
80
|
+
}: WebsiteImageProps): ReactNode {
|
|
81
|
+
const attrs: Record<string, unknown> = {
|
|
82
|
+
'data-website-primitive': 'image',
|
|
83
|
+
src,
|
|
84
|
+
alt,
|
|
85
|
+
width,
|
|
86
|
+
height,
|
|
87
|
+
loading,
|
|
88
|
+
decoding,
|
|
89
|
+
};
|
|
90
|
+
if (className !== undefined) attrs.className = className;
|
|
91
|
+
if (style !== undefined) attrs.style = style;
|
|
92
|
+
if (id !== undefined) attrs.id = id;
|
|
93
|
+
return <img {...attrs} />;
|
|
94
|
+
}
|
|
95
|
+
WebsiteImage.displayName = 'WebsiteImage';
|
|
96
|
+
/** @wildo_source:part:end saas.website.primitives.image */
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import React, { type CSSProperties, type ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
import { AvailableLanguage } from '@wildo-ai/saas-models/public-runtime';
|
|
4
|
+
|
|
5
|
+
import { useWebsiteRuntime } from '../../core/contexts/useWebsiteRuntime';
|
|
6
|
+
import { buildLocalizedRoutePath } from '../../core/routing/internal-routing.utils';
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
WebsiteButton,
|
|
10
|
+
type WebsiteButtonSize,
|
|
11
|
+
type WebsiteButtonVariant,
|
|
12
|
+
} from './WebsiteButton';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Pre-computed set of every known locale code; mirrors the guard in
|
|
16
|
+
* `WebsiteInternalLink` (kept duplicated rather than shared so each
|
|
17
|
+
* primitive's audit surface stays self-contained — the set is tiny).
|
|
18
|
+
*/
|
|
19
|
+
const ALL_LOCALE_CODES: ReadonlySet<string> = new Set(
|
|
20
|
+
Object.values(AvailableLanguage) as readonly string[],
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
function detectAccidentalLocalePrefix(routePath: string): string | null {
|
|
24
|
+
const slashIdx = routePath.indexOf('/', 1);
|
|
25
|
+
const firstSegment =
|
|
26
|
+
slashIdx === -1 ? routePath.slice(1) : routePath.slice(1, slashIdx);
|
|
27
|
+
if (firstSegment === '') return null;
|
|
28
|
+
return ALL_LOCALE_CODES.has(firstSegment) ? firstSegment : null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @wildo_source:part:start saas.website.primitives.internal-button facet:layer:components facet:family:website
|
|
33
|
+
*
|
|
34
|
+
* `WebsiteInternalButton` — L1 marketing-site CTA primitive for
|
|
35
|
+
* INTERNAL anchor-mode buttons (CTAs that navigate to another route
|
|
36
|
+
* served by the same Astro build, e.g. "Get in touch" → `/contact`).
|
|
37
|
+
*
|
|
38
|
+
* **The architectural problem this solves** (Phase 6 fourth audit fix
|
|
39
|
+
* H-Φ): the H-β fix shipped `WebsiteInternalLink` for header/footer
|
|
40
|
+
* navigation links but `WebsiteButton` consumers in section bodies
|
|
41
|
+
* (hero / features / pricing / contact) continued to write
|
|
42
|
+
* `<WebsiteButton href="/contact">` with raw locale-neutral paths. On
|
|
43
|
+
* `/fr/…` pages those CTAs navigated to the EN-default `/contact`
|
|
44
|
+
* URL — exactly the cross-locale leak H-β was meant to kill, just on
|
|
45
|
+
* the page body instead of the chrome. Authors had to know the
|
|
46
|
+
* routing rule and call `buildLocalizedRoutePath` manually on every
|
|
47
|
+
* CTA they shipped, and the audit confirmed they did not.
|
|
48
|
+
*
|
|
49
|
+
* **Why a parallel primitive** (mirrors the H-β reasoning, NOT a
|
|
50
|
+
* `routePath` flag on `WebsiteButton`):
|
|
51
|
+
* 1. The two primitives have different authoring contracts —
|
|
52
|
+
* `WebsiteButton`'s `href` is a fully-qualified URL the author
|
|
53
|
+
* already commits to (or an anchor `#section`, or external);
|
|
54
|
+
* this primitive's `routePath` is a raw locale-neutral path that
|
|
55
|
+
* the framework prefixes. Mixing both shapes behind one prop
|
|
56
|
+
* forces the author to think about a routing rule on every CTA
|
|
57
|
+
* they place; mixing them without a type-system signal invites
|
|
58
|
+
* the wrong shape on the wrong branch.
|
|
59
|
+
* 2. Internal CTAs REQUIRE the runtime context; external/anchor
|
|
60
|
+
* CTAs do not. Forcing every external-link `WebsiteButton`
|
|
61
|
+
* consumer to wrap in `<WebsiteRuntimeContextProvider>` is wrong.
|
|
62
|
+
* Two primitives keep the dependency surface honest.
|
|
63
|
+
* 3. Symmetric with `WebsiteInternalLink` — section authors learn
|
|
64
|
+
* one rule ("use the `Internal` variant for app-served paths,
|
|
65
|
+
* the bare variant for external/scheme/anchor URLs") and apply
|
|
66
|
+
* it identically to both link and button shapes.
|
|
67
|
+
*
|
|
68
|
+
* **Button-only render mode**: this primitive is anchor-mode-only by
|
|
69
|
+
* construction (every internal target is a route → an anchor element
|
|
70
|
+
* with appropriate href). Consumers who need a real `<button>` (no
|
|
71
|
+
* navigation, just an `onClick` handler) keep using `<WebsiteButton>`
|
|
72
|
+
* directly — there is no `routePath`-based real-button concept.
|
|
73
|
+
*
|
|
74
|
+
* **Anchor-only links** (`routePath="#features"`) and URL schemes
|
|
75
|
+
* (`mailto:`/`tel:`) are rejected at runtime: those targets do NOT
|
|
76
|
+
* benefit from locale prefixing, and surfacing them through this
|
|
77
|
+
* primitive would invite the SAME confusion that motivated splitting
|
|
78
|
+
* `WebsiteInternalLink` from `WebsiteLink` in the first place. Use
|
|
79
|
+
* `<WebsiteButton href="..." external={true}>` for those cases.
|
|
80
|
+
*
|
|
81
|
+
* **Boundary**: this component MUST stay in the React-island bundle
|
|
82
|
+
* tier (`components/low-level/`). It depends on `useWebsiteRuntime`
|
|
83
|
+
* (which throws when called outside a `<WebsiteRuntimeContextProvider>`)
|
|
84
|
+
* — the throw is the contract that prevents body CTAs from being
|
|
85
|
+
* rendered outside a bridged page accidentally.
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* ```tsx
|
|
89
|
+
* // Hero CTA — auto-prefixes the active locale
|
|
90
|
+
* // (`/contact` on EN pages, `/fr/contact` on FR pages):
|
|
91
|
+
* <WebsiteInternalButton routePath="/contact" variant={WebsiteButtonVariant.PRIMARY}>
|
|
92
|
+
* {useWebsiteLabel('cta.primary')}
|
|
93
|
+
* </WebsiteInternalButton>
|
|
94
|
+
* ```
|
|
95
|
+
*/
|
|
96
|
+
export interface WebsiteInternalButtonProps {
|
|
97
|
+
/**
|
|
98
|
+
* The locale-NEUTRAL route path. Must match a value the consumer's
|
|
99
|
+
* Astro build serves (typically a `WebsitePageManifest.routePath`).
|
|
100
|
+
* MUST start with `/` — anchor-only `#section` and URL schemes
|
|
101
|
+
* (`mailto:` / `tel:`) are rejected (use `<WebsiteButton href={...}>`
|
|
102
|
+
* for those cases).
|
|
103
|
+
*/
|
|
104
|
+
routePath: string;
|
|
105
|
+
variant?: WebsiteButtonVariant;
|
|
106
|
+
size?: WebsiteButtonSize;
|
|
107
|
+
className?: string;
|
|
108
|
+
style?: CSSProperties;
|
|
109
|
+
id?: string;
|
|
110
|
+
ariaLabel?: string;
|
|
111
|
+
children: ReactNode;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function WebsiteInternalButton({
|
|
115
|
+
routePath,
|
|
116
|
+
variant,
|
|
117
|
+
size,
|
|
118
|
+
className,
|
|
119
|
+
style,
|
|
120
|
+
id,
|
|
121
|
+
ariaLabel,
|
|
122
|
+
children,
|
|
123
|
+
}: WebsiteInternalButtonProps): ReactNode {
|
|
124
|
+
const runtime = useWebsiteRuntime();
|
|
125
|
+
|
|
126
|
+
if (!routePath.startsWith('/')) {
|
|
127
|
+
throw new Error(
|
|
128
|
+
`[WebsiteInternalButton] routePath="${routePath}" must start with "/" ` +
|
|
129
|
+
`(framework-relative path). Anchors ("#..."), URL schemes ` +
|
|
130
|
+
`("mailto:" / "tel:"), and fully-qualified external URLs are ` +
|
|
131
|
+
`NOT supported by this primitive — use <WebsiteButton href={...} external={true}> ` +
|
|
132
|
+
`(or omit \`external\` for in-app anchors and scheme links). ` +
|
|
133
|
+
`For NAVIGATION links (not styled as a CTA), see <WebsiteInternalLink routePath={...}>.`,
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const accidentalPrefix = detectAccidentalLocalePrefix(routePath);
|
|
138
|
+
if (accidentalPrefix !== null) {
|
|
139
|
+
throw new Error(
|
|
140
|
+
`[WebsiteInternalButton] routePath="${routePath}" begins with the ` +
|
|
141
|
+
`locale segment "/${accidentalPrefix}" — the framework prefixes ` +
|
|
142
|
+
`the active locale automatically, so passing a pre-prefixed path ` +
|
|
143
|
+
`would render as "/${accidentalPrefix}${routePath}" on non-default ` +
|
|
144
|
+
`locale pages (double-prefix) or silently break locale routing on ` +
|
|
145
|
+
`default-locale pages. Pass the locale-NEUTRAL path instead ` +
|
|
146
|
+
`(eg. "/pricing", not "/${accidentalPrefix}/pricing").`,
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const href = buildLocalizedRoutePath({
|
|
151
|
+
locale: runtime.locale,
|
|
152
|
+
defaultLocale: runtime.defaultLocale,
|
|
153
|
+
routePath,
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
return (
|
|
157
|
+
<WebsiteButton
|
|
158
|
+
href={href}
|
|
159
|
+
external={false}
|
|
160
|
+
variant={variant}
|
|
161
|
+
size={size}
|
|
162
|
+
className={className}
|
|
163
|
+
style={style}
|
|
164
|
+
id={id}
|
|
165
|
+
ariaLabel={ariaLabel}
|
|
166
|
+
>
|
|
167
|
+
{children}
|
|
168
|
+
</WebsiteButton>
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
WebsiteInternalButton.displayName = 'WebsiteInternalButton';
|
|
172
|
+
/** @wildo_source:part:end saas.website.primitives.internal-button */
|