@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
|
@@ -0,0 +1,193 @@
|
|
|
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 { WebsiteLink } from './WebsiteLink';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @wildo_source:part:start saas.website.primitives.internal-link facet:layer:components facet:family:website
|
|
12
|
+
*
|
|
13
|
+
* `WebsiteInternalLink` — L1 marketing-site link primitive for
|
|
14
|
+
* INTERNAL routes (any path served by the same Astro build).
|
|
15
|
+
*
|
|
16
|
+
* **The architectural problem this solves** (Phase 6 third audit fix
|
|
17
|
+
* H-β): the marketing site mounts the default locale at the
|
|
18
|
+
* un-prefixed path (`/`, `/pricing`) and every other locale under
|
|
19
|
+
* `/<locale>/…` (`/fr/`, `/fr/pricing`). A naive `<a href="/pricing">`
|
|
20
|
+
* authored in the chrome therefore works on EN pages and SILENTLY
|
|
21
|
+
* BREAKS on FR pages — the visitor falls out of their locale on every
|
|
22
|
+
* navigation. The dist-HTML audit confirmed the regression.
|
|
23
|
+
*
|
|
24
|
+
* **Why a dedicated primitive** (parallel to `WebsiteLink`, not a flag
|
|
25
|
+
* on it):
|
|
26
|
+
* 1. The two primitives have different authoring contracts — an
|
|
27
|
+
* external link's `href` is a fully-qualified URL the author
|
|
28
|
+
* already commits to; an internal link's `href` is a raw
|
|
29
|
+
* `routePath` (`'/pricing'`) that the framework prefixes. Mixing
|
|
30
|
+
* both shapes behind one `external?` prop forces the author to
|
|
31
|
+
* think about a routing rule on every link they place; mixing
|
|
32
|
+
* them behind one component invites the wrong shape on the wrong
|
|
33
|
+
* branch (an internal href passed with `external` set, or vice
|
|
34
|
+
* versa) without a type-system signal.
|
|
35
|
+
* 2. Internal links REQUIRE the runtime context; external links do
|
|
36
|
+
* not. Forcing every external-link consumer to wrap in
|
|
37
|
+
* `<WebsiteRuntimeContextProvider>` (or pay the
|
|
38
|
+
* throw-on-missing-context cost) is wrong. Two primitives keep
|
|
39
|
+
* the dependency surface honest.
|
|
40
|
+
* 3. The framework can layer additional internal-link concerns
|
|
41
|
+
* later (active-route highlighting, prefetch directives,
|
|
42
|
+
* router-aware navigation when the consumer overrides
|
|
43
|
+
* `WebsiteLink`) without complicating the external-link
|
|
44
|
+
* contract.
|
|
45
|
+
*
|
|
46
|
+
* **Why `routePath` is a string and NOT `WebsitePageRef`**:
|
|
47
|
+
* - Many internal targets are NOT pages (anchor links to a section
|
|
48
|
+
* within the current page: `'#features'`; same-locale subroutes
|
|
49
|
+
* served by the consumer's Astro build but not registered as
|
|
50
|
+
* `WebsitePageManifest` entries: `'/blog/launch'`). A `pageRef`
|
|
51
|
+
* prop would force every link target to be a registered manifest,
|
|
52
|
+
* which is too restrictive for chrome.
|
|
53
|
+
* - The consumer's chrome typically links to a small fixed set of
|
|
54
|
+
* `routePath` values they author once. The boilerplate cost of
|
|
55
|
+
* looking up the matching manifest by ref is non-trivial; the
|
|
56
|
+
* payoff (typo protection on `'/pricing'`) is small.
|
|
57
|
+
* - We DO validate at runtime that `routePath` starts with `/` and
|
|
58
|
+
* does not double-prefix (a path already starting with `/<locale>/`
|
|
59
|
+
* would otherwise become `/fr/fr/pricing` on a FR page) — see the
|
|
60
|
+
* in-render guard below.
|
|
61
|
+
*
|
|
62
|
+
* **Anchor-only links** (`href="#features"`) are passed through
|
|
63
|
+
* verbatim: they navigate within the current page, no locale prefix
|
|
64
|
+
* applies. Same for `mailto:` / `tel:` — recognized by colon presence
|
|
65
|
+
* and forwarded as-is.
|
|
66
|
+
*
|
|
67
|
+
* **Boundary**: this component MUST stay in the React-island bundle
|
|
68
|
+
* tier (`components/low-level/`). It depends on `useWebsiteRuntime`
|
|
69
|
+
* (which throws when called outside a `<WebsiteRuntimeContextProvider>`)
|
|
70
|
+
* — the throw is the contract that prevents the chrome from being
|
|
71
|
+
* rendered outside a bridged page accidentally.
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* ```tsx
|
|
75
|
+
* // In HeaderComponent.tsx — the framework computes the right URL
|
|
76
|
+
* // automatically (`/pricing` on EN pages, `/fr/pricing` on FR
|
|
77
|
+
* // pages):
|
|
78
|
+
* <WebsiteInternalLink routePath="/pricing">
|
|
79
|
+
* <WebsiteText variant={WebsiteTextVariant.BODY}>Pricing</WebsiteText>
|
|
80
|
+
* </WebsiteInternalLink>
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
export interface WebsiteInternalLinkProps {
|
|
84
|
+
/**
|
|
85
|
+
* The locale-NEUTRAL route path. Must match a value the consumer's
|
|
86
|
+
* Astro build serves (typically a `WebsitePageManifest.routePath`,
|
|
87
|
+
* but anchor-only `#section` and same-page subroutes are also
|
|
88
|
+
* accepted). MUST start with `/` (or `#` for in-page anchors, or
|
|
89
|
+
* include `:` for `mailto:` / `tel:`).
|
|
90
|
+
*/
|
|
91
|
+
routePath: string;
|
|
92
|
+
className?: string;
|
|
93
|
+
style?: CSSProperties;
|
|
94
|
+
id?: string;
|
|
95
|
+
/**
|
|
96
|
+
* Optional `aria-label` for icon-only links (where the link text
|
|
97
|
+
* is a glyph or sr-only).
|
|
98
|
+
*/
|
|
99
|
+
ariaLabel?: string;
|
|
100
|
+
children: ReactNode;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function isAnchorOrUrlScheme(routePath: string): boolean {
|
|
104
|
+
return routePath.startsWith('#') || routePath.includes(':');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Pre-computed set of every known locale code (`'en'`, `'fr'`,
|
|
109
|
+
* `'en-GB'`, …). Built ONCE at module load from `AvailableLanguage`
|
|
110
|
+
* so the per-render double-prefix guard below is an O(1) Set.has
|
|
111
|
+
* check rather than an Object.values() walk on every link render.
|
|
112
|
+
*/
|
|
113
|
+
const ALL_LOCALE_CODES: ReadonlySet<string> = new Set(
|
|
114
|
+
Object.values(AvailableLanguage) as readonly string[],
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Defensive in-render guard: rejects route paths whose first segment
|
|
119
|
+
* matches a known locale code. The marketing-site convention is that
|
|
120
|
+
* the consumer authors locale-NEUTRAL paths and the framework
|
|
121
|
+
* prefixes them; an already-prefixed path would either render as a
|
|
122
|
+
* `/<locale>/<locale>/pricing` double-prefix on non-default-locale
|
|
123
|
+
* pages or silently work on default-locale pages and break on other
|
|
124
|
+
* locales — both are silent regressions. Throwing here makes the
|
|
125
|
+
* misuse loud at first render.
|
|
126
|
+
*
|
|
127
|
+
* Returns the offending locale segment if the path is misused, or
|
|
128
|
+
* `null` if the path is well-formed.
|
|
129
|
+
*/
|
|
130
|
+
function detectAccidentalLocalePrefix(routePath: string): string | null {
|
|
131
|
+
const slashIdx = routePath.indexOf('/', 1);
|
|
132
|
+
const firstSegment =
|
|
133
|
+
slashIdx === -1 ? routePath.slice(1) : routePath.slice(1, slashIdx);
|
|
134
|
+
if (firstSegment === '') return null;
|
|
135
|
+
return ALL_LOCALE_CODES.has(firstSegment) ? firstSegment : null;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function WebsiteInternalLink({
|
|
139
|
+
routePath,
|
|
140
|
+
className,
|
|
141
|
+
style,
|
|
142
|
+
id,
|
|
143
|
+
ariaLabel,
|
|
144
|
+
children,
|
|
145
|
+
}: WebsiteInternalLinkProps): ReactNode {
|
|
146
|
+
const runtime = useWebsiteRuntime();
|
|
147
|
+
|
|
148
|
+
let href: string;
|
|
149
|
+
if (isAnchorOrUrlScheme(routePath)) {
|
|
150
|
+
href = routePath;
|
|
151
|
+
} else {
|
|
152
|
+
if (!routePath.startsWith('/')) {
|
|
153
|
+
throw new Error(
|
|
154
|
+
`[WebsiteInternalLink] routePath="${routePath}" must start with "/" ` +
|
|
155
|
+
`(framework-relative path), "#" (in-page anchor) or include ":" ` +
|
|
156
|
+
`(URL scheme like mailto:/tel:). Use <WebsiteLink href={...} external={true}> ` +
|
|
157
|
+
`for fully-qualified external URLs.`,
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
const accidentalPrefix = detectAccidentalLocalePrefix(routePath);
|
|
161
|
+
if (accidentalPrefix !== null) {
|
|
162
|
+
throw new Error(
|
|
163
|
+
`[WebsiteInternalLink] routePath="${routePath}" begins with the ` +
|
|
164
|
+
`locale segment "/${accidentalPrefix}" — the framework prefixes ` +
|
|
165
|
+
`the active locale automatically, so passing a pre-prefixed path ` +
|
|
166
|
+
`would render as "/${accidentalPrefix}${routePath}" on non-default ` +
|
|
167
|
+
`locale pages (double-prefix) or silently break locale routing on ` +
|
|
168
|
+
`default-locale pages. Pass the locale-NEUTRAL path instead ` +
|
|
169
|
+
`(eg. "/pricing", not "/${accidentalPrefix}/pricing").`,
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
href = buildLocalizedRoutePath({
|
|
173
|
+
locale: runtime.locale,
|
|
174
|
+
defaultLocale: runtime.defaultLocale,
|
|
175
|
+
routePath,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return (
|
|
180
|
+
<WebsiteLink
|
|
181
|
+
href={href}
|
|
182
|
+
external={false}
|
|
183
|
+
className={className}
|
|
184
|
+
style={style}
|
|
185
|
+
id={id}
|
|
186
|
+
ariaLabel={ariaLabel}
|
|
187
|
+
>
|
|
188
|
+
{children}
|
|
189
|
+
</WebsiteLink>
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
WebsiteInternalLink.displayName = 'WebsiteInternalLink';
|
|
193
|
+
/** @wildo_source:part:end saas.website.primitives.internal-link */
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import React, { type CSSProperties, type ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @wildo_source:part:start saas.website.primitives.link facet:layer:components facet:family:website
|
|
5
|
+
*
|
|
6
|
+
* `WebsiteLink` — L1 marketing-site link primitive.
|
|
7
|
+
*
|
|
8
|
+
* **Status (Phase 2 / D18)**: minimal placeholder. Renders a plain
|
|
9
|
+
* `<a>` with `data-website-primitive="link"` and forwards href +
|
|
10
|
+
* standard link attributes. Consumers override at the L1 boundary
|
|
11
|
+
* to wire in their preferred client-side router (Astro `<a>` does
|
|
12
|
+
* full navigation by default; consumers using View Transitions or
|
|
13
|
+
* a router-aware link wrap that here).
|
|
14
|
+
*
|
|
15
|
+
* **Why `external` is an explicit prop** (vs. inferred from the
|
|
16
|
+
* href shape): inference is unreliable (relative paths from a CMS
|
|
17
|
+
* may resolve to external; protocol-relative URLs are external but
|
|
18
|
+
* don't start with `http`). An explicit prop forces the section
|
|
19
|
+
* author to decide. When `external === true`, the primitive applies
|
|
20
|
+
* `rel="noopener noreferrer"` and `target="_blank"` automatically
|
|
21
|
+
* — security best-practice baked in.
|
|
22
|
+
*
|
|
23
|
+
* **Why no `variant`**: link visual variants (button-like CTA,
|
|
24
|
+
* inline-text link, icon-only) are too tightly coupled to the
|
|
25
|
+
* design system to be useful as a placeholder vocabulary. The
|
|
26
|
+
* override implementation defines its own variants; the
|
|
27
|
+
* placeholder ships only the security-correct attribute defaults.
|
|
28
|
+
*/
|
|
29
|
+
export interface WebsiteLinkProps {
|
|
30
|
+
href: string;
|
|
31
|
+
/**
|
|
32
|
+
* Marks the link as external. Adds `target="_blank"` and
|
|
33
|
+
* `rel="noopener noreferrer"`. Defaults to `false`.
|
|
34
|
+
*/
|
|
35
|
+
external?: boolean;
|
|
36
|
+
className?: string;
|
|
37
|
+
style?: CSSProperties;
|
|
38
|
+
id?: string;
|
|
39
|
+
/**
|
|
40
|
+
* Optional `aria-label` for icon-only links (where the link text
|
|
41
|
+
* is a glyph or sr-only).
|
|
42
|
+
*/
|
|
43
|
+
ariaLabel?: string;
|
|
44
|
+
children: ReactNode;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function WebsiteLink({
|
|
48
|
+
href,
|
|
49
|
+
external = false,
|
|
50
|
+
className,
|
|
51
|
+
style,
|
|
52
|
+
id,
|
|
53
|
+
ariaLabel,
|
|
54
|
+
children,
|
|
55
|
+
}: WebsiteLinkProps): ReactNode {
|
|
56
|
+
const attrs: Record<string, unknown> = {
|
|
57
|
+
'data-website-primitive': 'link',
|
|
58
|
+
href,
|
|
59
|
+
};
|
|
60
|
+
if (external) {
|
|
61
|
+
attrs.target = '_blank';
|
|
62
|
+
attrs.rel = 'noopener noreferrer';
|
|
63
|
+
}
|
|
64
|
+
if (className !== undefined) attrs.className = className;
|
|
65
|
+
if (style !== undefined) attrs.style = style;
|
|
66
|
+
if (id !== undefined) attrs.id = id;
|
|
67
|
+
if (ariaLabel !== undefined) attrs['aria-label'] = ariaLabel;
|
|
68
|
+
return <a {...attrs}>{children}</a>;
|
|
69
|
+
}
|
|
70
|
+
WebsiteLink.displayName = 'WebsiteLink';
|
|
71
|
+
/** @wildo_source:part:end saas.website.primitives.link */
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import React, { type CSSProperties, type ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @wildo_source:part:start saas.website.primitives.text facet:layer:components facet:family:website
|
|
5
|
+
*
|
|
6
|
+
* `WebsiteText` — L1 marketing-site body-text primitive.
|
|
7
|
+
*
|
|
8
|
+
* Same Phase 2 / D18 status as `WebsiteHeading` — minimal,
|
|
9
|
+
* self-contained placeholder; consumers override at the L1 boundary
|
|
10
|
+
* via barrel re-export. The skill-driven authoring path will require
|
|
11
|
+
* an override before shipping a public marketing site.
|
|
12
|
+
*
|
|
13
|
+
* **Why `variant` (lead vs. body vs. caption) is shipped here**:
|
|
14
|
+
* marketing-site body copy has a distinct typographic scale from
|
|
15
|
+
* in-app body copy (looser line-height for scannability, larger
|
|
16
|
+
* `lead` paragraphs under headlines, smaller `caption` under
|
|
17
|
+
* pricing tier features). The variant slot exists at the
|
|
18
|
+
* placeholder level so section authors call the same prop the
|
|
19
|
+
* override implementation honors.
|
|
20
|
+
*
|
|
21
|
+
* **Why default tag is `<p>`**: the overwhelming majority of
|
|
22
|
+
* marketing copy is a paragraph. The `as` prop allows escaping to
|
|
23
|
+
* `<span>` (inline copy inside a heading), `<strong>` (emphatic
|
|
24
|
+
* inline), or `<em>` (italicized inline) without compromising the
|
|
25
|
+
* paragraph default.
|
|
26
|
+
*/
|
|
27
|
+
export enum WebsiteTextVariant {
|
|
28
|
+
/**
|
|
29
|
+
* Lead paragraph immediately under a section headline (slightly
|
|
30
|
+
* larger / less dense than body).
|
|
31
|
+
*/
|
|
32
|
+
LEAD = 'lead',
|
|
33
|
+
/**
|
|
34
|
+
* Standard body copy. The default.
|
|
35
|
+
*/
|
|
36
|
+
BODY = 'body',
|
|
37
|
+
/**
|
|
38
|
+
* Small / supporting copy (footnotes, captions, fine print).
|
|
39
|
+
*/
|
|
40
|
+
CAPTION = 'caption',
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export type WebsiteTextElement = 'p' | 'span' | 'strong' | 'em' | 'div';
|
|
44
|
+
|
|
45
|
+
export interface WebsiteTextProps {
|
|
46
|
+
/** Visual variant. Defaults to `BODY`. */
|
|
47
|
+
variant?: WebsiteTextVariant;
|
|
48
|
+
/** Rendered tag. Defaults to `'p'`. */
|
|
49
|
+
as?: WebsiteTextElement;
|
|
50
|
+
className?: string;
|
|
51
|
+
style?: CSSProperties;
|
|
52
|
+
id?: string;
|
|
53
|
+
children: ReactNode;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function WebsiteText({
|
|
57
|
+
variant = WebsiteTextVariant.BODY,
|
|
58
|
+
as = 'p',
|
|
59
|
+
className,
|
|
60
|
+
style,
|
|
61
|
+
id,
|
|
62
|
+
children,
|
|
63
|
+
}: WebsiteTextProps): ReactNode {
|
|
64
|
+
const Tag = as;
|
|
65
|
+
const attrs: Record<string, unknown> = {
|
|
66
|
+
'data-website-primitive': 'text',
|
|
67
|
+
'data-website-text-variant': variant,
|
|
68
|
+
};
|
|
69
|
+
if (className !== undefined) attrs.className = className;
|
|
70
|
+
if (style !== undefined) attrs.style = style;
|
|
71
|
+
if (id !== undefined) attrs.id = id;
|
|
72
|
+
return <Tag {...attrs}>{children}</Tag>;
|
|
73
|
+
}
|
|
74
|
+
WebsiteText.displayName = 'WebsiteText';
|
|
75
|
+
/** @wildo_source:part:end saas.website.primitives.text */
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
3
|
+
import { render } from '@testing-library/react';
|
|
4
|
+
import '@testing-library/jest-dom';
|
|
5
|
+
|
|
6
|
+
import { AvailableLanguage } from '@wildo-ai/saas-models/public-runtime';
|
|
7
|
+
|
|
8
|
+
import { WebsiteRuntimeContextProvider } from '../../../core/contexts/WebsiteRuntimeContext';
|
|
9
|
+
import { WebsiteLabelKeySchema } from '../../../schemas/label-keys/website-label-key.schemas';
|
|
10
|
+
|
|
11
|
+
import { WebsiteInternalButton } from '../WebsiteInternalButton';
|
|
12
|
+
import { WebsiteButtonSize, WebsiteButtonVariant } from '../WebsiteButton';
|
|
13
|
+
|
|
14
|
+
const SKIP_KEY = WebsiteLabelKeySchema.parse('website.chrome.layout.skip-to-main-content');
|
|
15
|
+
|
|
16
|
+
function renderWithRuntime(
|
|
17
|
+
ui: React.ReactNode,
|
|
18
|
+
opts: { locale: AvailableLanguage; defaultLocale: AvailableLanguage },
|
|
19
|
+
): ReturnType<typeof render> {
|
|
20
|
+
return render(
|
|
21
|
+
<WebsiteRuntimeContextProvider
|
|
22
|
+
value={{
|
|
23
|
+
locale: opts.locale,
|
|
24
|
+
defaultLocale: opts.defaultLocale,
|
|
25
|
+
labelPack: {},
|
|
26
|
+
designTokens: {
|
|
27
|
+
colors: {
|
|
28
|
+
background: '#fff',
|
|
29
|
+
foreground: '#111',
|
|
30
|
+
primary: '#0070f3',
|
|
31
|
+
primaryForeground: '#fff',
|
|
32
|
+
accent: '#ff5e5b',
|
|
33
|
+
accentForeground: '#fff',
|
|
34
|
+
surface: '#fafafa',
|
|
35
|
+
surfaceForeground: '#111',
|
|
36
|
+
border: '#e5e5e5',
|
|
37
|
+
muted: '#f5f5f5',
|
|
38
|
+
mutedForeground: '#737373',
|
|
39
|
+
},
|
|
40
|
+
radii: {
|
|
41
|
+
none: '0',
|
|
42
|
+
sm: '0.125rem',
|
|
43
|
+
md: '0.375rem',
|
|
44
|
+
lg: '0.75rem',
|
|
45
|
+
xl: '1.5rem',
|
|
46
|
+
full: '9999px',
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
navigation: {
|
|
50
|
+
headerComponent: () => null,
|
|
51
|
+
footerComponent: () => null,
|
|
52
|
+
},
|
|
53
|
+
skipToMainContentLabelKey: SKIP_KEY,
|
|
54
|
+
}}
|
|
55
|
+
>
|
|
56
|
+
{ui}
|
|
57
|
+
</WebsiteRuntimeContextProvider>,
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
describe('WebsiteInternalButton', () => {
|
|
62
|
+
describe('default-locale page (un-prefixed routing)', () => {
|
|
63
|
+
it('renders the routePath verbatim when locale === defaultLocale', () => {
|
|
64
|
+
const { container } = renderWithRuntime(
|
|
65
|
+
<WebsiteInternalButton routePath="/contact">Contact</WebsiteInternalButton>,
|
|
66
|
+
{ locale: AvailableLanguage.EN, defaultLocale: AvailableLanguage.EN },
|
|
67
|
+
);
|
|
68
|
+
expect(container.querySelector('a')?.getAttribute('href')).toBe('/contact');
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
describe('non-default-locale page (prefixed routing)', () => {
|
|
73
|
+
it('prefixes /<locale> on FR pages', () => {
|
|
74
|
+
const { container } = renderWithRuntime(
|
|
75
|
+
<WebsiteInternalButton routePath="/contact">Contactez-nous</WebsiteInternalButton>,
|
|
76
|
+
{ locale: AvailableLanguage.FR, defaultLocale: AvailableLanguage.EN },
|
|
77
|
+
);
|
|
78
|
+
expect(container.querySelector('a')?.getAttribute('href')).toBe('/fr/contact');
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('collapses root to /<locale> (NOT /<locale>/)', () => {
|
|
82
|
+
const { container } = renderWithRuntime(
|
|
83
|
+
<WebsiteInternalButton routePath="/">Home</WebsiteInternalButton>,
|
|
84
|
+
{ locale: AvailableLanguage.FR, defaultLocale: AvailableLanguage.EN },
|
|
85
|
+
);
|
|
86
|
+
expect(container.querySelector('a')?.getAttribute('href')).toBe('/fr');
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe('misuse guards', () => {
|
|
91
|
+
it('throws when routePath does not start with /', () => {
|
|
92
|
+
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
93
|
+
expect(() =>
|
|
94
|
+
renderWithRuntime(
|
|
95
|
+
<WebsiteInternalButton routePath="contact">Contact</WebsiteInternalButton>,
|
|
96
|
+
{ locale: AvailableLanguage.EN, defaultLocale: AvailableLanguage.EN },
|
|
97
|
+
),
|
|
98
|
+
).toThrow(/must start with/i);
|
|
99
|
+
consoleErrorSpy.mockRestore();
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('rejects anchor-only routePaths (use WebsiteButton instead)', () => {
|
|
103
|
+
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
104
|
+
expect(() =>
|
|
105
|
+
renderWithRuntime(
|
|
106
|
+
<WebsiteInternalButton routePath="#features">Features</WebsiteInternalButton>,
|
|
107
|
+
{ locale: AvailableLanguage.EN, defaultLocale: AvailableLanguage.EN },
|
|
108
|
+
),
|
|
109
|
+
).toThrow(/must start with "\/"/i);
|
|
110
|
+
consoleErrorSpy.mockRestore();
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('rejects URL schemes (mailto: / tel:)', () => {
|
|
114
|
+
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
115
|
+
expect(() =>
|
|
116
|
+
renderWithRuntime(
|
|
117
|
+
<WebsiteInternalButton routePath="mailto:hi@wildo.ai">Email</WebsiteInternalButton>,
|
|
118
|
+
{ locale: AvailableLanguage.EN, defaultLocale: AvailableLanguage.EN },
|
|
119
|
+
),
|
|
120
|
+
).toThrow(/must start with "\/"/i);
|
|
121
|
+
consoleErrorSpy.mockRestore();
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('throws when used outside <WebsiteRuntimeContextProvider>', () => {
|
|
125
|
+
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
126
|
+
expect(() =>
|
|
127
|
+
render(<WebsiteInternalButton routePath="/contact">Contact</WebsiteInternalButton>),
|
|
128
|
+
).toThrow(/WebsiteRuntimeContextProvider/);
|
|
129
|
+
consoleErrorSpy.mockRestore();
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
describe('attribute / variant forwarding', () => {
|
|
134
|
+
it('forwards variant + size to the underlying WebsiteButton', () => {
|
|
135
|
+
const { container } = renderWithRuntime(
|
|
136
|
+
<WebsiteInternalButton
|
|
137
|
+
routePath="/contact"
|
|
138
|
+
variant={WebsiteButtonVariant.SECONDARY}
|
|
139
|
+
size={WebsiteButtonSize.LG}
|
|
140
|
+
>
|
|
141
|
+
Contact
|
|
142
|
+
</WebsiteInternalButton>,
|
|
143
|
+
{ locale: AvailableLanguage.EN, defaultLocale: AvailableLanguage.EN },
|
|
144
|
+
);
|
|
145
|
+
const span = container.querySelector('[data-website-primitive="button"]');
|
|
146
|
+
expect(span?.getAttribute('data-website-button-variant')).toBe('secondary');
|
|
147
|
+
expect(span?.getAttribute('data-website-button-size')).toBe('lg');
|
|
148
|
+
expect(span?.getAttribute('data-website-button-mode')).toBe('link');
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('forwards className, id, ariaLabel to the rendered anchor', () => {
|
|
152
|
+
const { container } = renderWithRuntime(
|
|
153
|
+
<WebsiteInternalButton
|
|
154
|
+
routePath="/contact"
|
|
155
|
+
className="wt-cta"
|
|
156
|
+
id="hero-cta"
|
|
157
|
+
ariaLabel="Open contact page"
|
|
158
|
+
>
|
|
159
|
+
Contact
|
|
160
|
+
</WebsiteInternalButton>,
|
|
161
|
+
{ locale: AvailableLanguage.EN, defaultLocale: AvailableLanguage.EN },
|
|
162
|
+
);
|
|
163
|
+
const a = container.querySelector('a');
|
|
164
|
+
expect(a?.getAttribute('class')).toBe('wt-cta');
|
|
165
|
+
expect(a?.getAttribute('id')).toBe('hero-cta');
|
|
166
|
+
expect(a?.getAttribute('aria-label')).toBe('Open contact page');
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
});
|