bsmnt 0.10.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/application/add-integration/index.js.map +1 -1
- package/dist/application/shared/node-version.js.map +1 -1
- package/dist/core/create/internal-ids.d.ts +3 -0
- package/dist/core/create/internal-ids.d.ts.map +1 -1
- package/dist/core/create/internal-ids.js +4 -0
- package/dist/core/create/internal-ids.js.map +1 -1
- package/dist/domain/agents.d.ts.map +1 -1
- package/dist/domain/animations.d.ts.map +1 -1
- package/dist/domain/cms.d.ts.map +1 -1
- package/dist/domain/forms.d.ts.map +1 -1
- package/dist/domain/hooks.d.ts.map +1 -1
- package/dist/domain/skills.d.ts.map +1 -1
- package/dist/domain/templates.d.ts +10 -0
- package/dist/domain/templates.d.ts.map +1 -1
- package/dist/domain/templates.js +5 -0
- package/dist/domain/templates.js.map +1 -1
- package/dist/index.js +18 -5
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/agents/register-sanity-mcp.js +3 -8
- package/dist/infrastructure/agents/register-sanity-mcp.js.map +1 -1
- package/dist/infrastructure/agents/register-storybook-mcp.d.ts +18 -0
- package/dist/infrastructure/agents/register-storybook-mcp.d.ts.map +1 -0
- package/dist/infrastructure/agents/register-storybook-mcp.js +157 -0
- package/dist/infrastructure/agents/register-storybook-mcp.js.map +1 -0
- package/dist/infrastructure/create/lifecycle-registry.d.ts.map +1 -1
- package/dist/infrastructure/create/lifecycle-registry.js +39 -1
- package/dist/infrastructure/create/lifecycle-registry.js.map +1 -1
- package/dist/infrastructure/create/merge-registry.d.ts.map +1 -1
- package/dist/infrastructure/create/merge-registry.js +4 -0
- package/dist/infrastructure/create/merge-registry.js.map +1 -1
- package/dist/infrastructure/create/sanity-auth.js.map +1 -1
- package/dist/infrastructure/create/setup-agent.d.ts +6 -1
- package/dist/infrastructure/create/setup-agent.d.ts.map +1 -1
- package/dist/infrastructure/create/setup-agent.js +23 -21
- package/dist/infrastructure/create/setup-agent.js.map +1 -1
- package/dist/infrastructure/create/setup-dotenvx.js.map +1 -1
- package/dist/infrastructure/create/setup-remote.js.map +1 -1
- package/dist/infrastructure/create/setup-sanity.d.ts.map +1 -1
- package/dist/infrastructure/create/setup-sanity.js +12 -1
- package/dist/infrastructure/create/setup-sanity.js.map +1 -1
- package/dist/infrastructure/integrations/inject-integration.d.ts.map +1 -1
- package/dist/infrastructure/integrations/inject-integration.js +2 -0
- package/dist/infrastructure/integrations/inject-integration.js.map +1 -1
- package/dist/infrastructure/update/notifier.js.map +1 -1
- package/dist/modules/features/cms/index.d.ts.map +1 -1
- package/dist/modules/features/cms/index.js +2 -0
- package/dist/modules/features/cms/index.js.map +1 -1
- package/dist/modules/features/cms/sanity/config.d.ts.map +1 -1
- package/dist/modules/features/cms/sanity/config.js +1 -0
- package/dist/modules/features/cms/sanity/config.js.map +1 -1
- package/dist/modules/features/cms/sanity/index.d.ts.map +1 -1
- package/dist/modules/features/cms/sanity/index.js +10 -1
- package/dist/modules/features/cms/sanity/index.js.map +1 -1
- package/dist/modules/features/cms/sanity/mergers/check-integration.d.ts.map +1 -1
- package/dist/modules/features/cms/sanity/mergers/check-integration.js +15 -6
- package/dist/modules/features/cms/sanity/mergers/check-integration.js.map +1 -1
- package/dist/modules/features/cms/sanity/mergers/knip.d.ts.map +1 -1
- package/dist/modules/features/cms/sanity/mergers/knip.js +1 -0
- package/dist/modules/features/cms/sanity/mergers/knip.js.map +1 -1
- package/dist/modules/features/cms/sanity/mergers/layout.js.map +1 -1
- package/dist/modules/features/cms/sanity/mergers/package-json.d.ts +12 -0
- package/dist/modules/features/cms/sanity/mergers/package-json.d.ts.map +1 -0
- package/dist/modules/features/cms/sanity/mergers/package-json.js +19 -0
- package/dist/modules/features/cms/sanity/mergers/package-json.js.map +1 -0
- package/dist/modules/features/cms/sanity-pagebuilder/index.d.ts.map +1 -1
- package/dist/modules/features/cms/sanity-pagebuilder/index.js +7 -1
- package/dist/modules/features/cms/sanity-pagebuilder/index.js.map +1 -1
- package/dist/modules/features/cms/sanity-pagebuilder-cache/config.d.ts +14 -0
- package/dist/modules/features/cms/sanity-pagebuilder-cache/config.d.ts.map +1 -0
- package/dist/modules/features/cms/sanity-pagebuilder-cache/config.js +50 -0
- package/dist/modules/features/cms/sanity-pagebuilder-cache/config.js.map +1 -0
- package/dist/modules/features/cms/sanity-pagebuilder-cache/index.d.ts +3 -0
- package/dist/modules/features/cms/sanity-pagebuilder-cache/index.d.ts.map +1 -0
- package/dist/modules/features/cms/sanity-pagebuilder-cache/index.js +55 -0
- package/dist/modules/features/cms/sanity-pagebuilder-cache/index.js.map +1 -0
- package/dist/modules/presets/index.d.ts.map +1 -1
- package/dist/modules/presets/index.js +2 -0
- package/dist/modules/presets/index.js.map +1 -1
- package/dist/modules/presets/next-pagebuilder-cache.d.ts +3 -0
- package/dist/modules/presets/next-pagebuilder-cache.d.ts.map +1 -0
- package/dist/modules/presets/next-pagebuilder-cache.js +10 -0
- package/dist/modules/presets/next-pagebuilder-cache.js.map +1 -0
- package/dist/paths.d.ts +6 -0
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +6 -0
- package/dist/paths.js.map +1 -1
- package/package.json +5 -2
- package/src/modules/features/cms/sanity/files/lib/integrations/check-integration.ts +7 -6
- package/src/modules/features/cms/sanity/files/lib/integrations/sanity/README.md +1 -1
- package/src/modules/features/cms/sanity/files/lib/integrations/sanity/components/rich-text.tsx +1 -0
- package/src/modules/features/cms/sanity/files/lib/integrations/sanity/env.ts +2 -2
- package/src/modules/features/cms/sanity-pagebuilder/files/app/api/[[...slug]]/route.ts +4 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/app/api/revalidate/route.ts +172 -24
- package/src/modules/features/cms/sanity-pagebuilder/files/app/feed.xml/route.ts +14 -7
- package/src/modules/features/cms/sanity-pagebuilder/files/app/sitemap.md/route.ts +2 -2
- package/src/modules/features/cms/sanity-pagebuilder/files/app/studio/[[...tool]]/page.tsx +16 -1
- package/src/modules/features/cms/sanity-pagebuilder/files/components/sanity/rich-text.tsx +1 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/components/sanity/visual-editing.tsx +2 -2
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/check-integration.ts +6 -5
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/client.ts +8 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/slug-notices.tsx +6 -16
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/env.ts +2 -2
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/fetchers/layout.ts +22 -52
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/icons.ts +2 -2
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/live/index.tsx +3 -1
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/page-builder-config.ts +7 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/page-tree-pane.tsx +70 -57
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/queries.ts +119 -29
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/revalidate-paths.ts +70 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/index.ts +1 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/reusable/rich-text-block.ts +39 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/index.ts +2 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/page.ts +12 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/tags.ts +56 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/utils/page-builder-markdown.ts +12 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/utils/sitemap.ts +11 -8
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/.knip.json +8 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/app/api/[[...slug]]/route.ts +112 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/app/api/draft-mode/disable/route.ts +7 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/app/api/draft-mode/enable/route.ts +20 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/app/api/revalidate/route.ts +292 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/app/feed.xml/route.ts +66 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/app/sitemap.md/route.ts +132 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/app/studio/[[...tool]]/layout.tsx +26 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/app/studio/[[...tool]]/page.tsx +23 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/components/sanity/draft-mode-toggle.tsx +27 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/components/sanity/rich-text.tsx +88 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/components/sanity/visual-editing.tsx +11 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/components/ui/sanity-image/index.tsx +51 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/check-integration.ts +6 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/client.ts +37 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/block-actions-item.tsx +81 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/disable-draft-mode.tsx +23 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/page-builder-input.tsx +39 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/page-category-input.tsx +50 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/same-as-input.tsx +66 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/slug-field.tsx +24 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/slug-input.tsx +14 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/slug-notices.tsx +125 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/confirm-publish-action.ts +40 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/env.ts +34 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/fetchers/layout.ts +39 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/fetchers/redirects.ts +22 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/icons.ts +81 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/live/index.tsx +140 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/markdown-proxy.config.ts +50 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/page-builder-config.ts +144 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/page-category.ts +28 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/page-tree-pane.tsx +431 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/pinned-pages.ts +54 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/presentation.ts +119 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/queries.ts +548 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/revalidate-paths.ts +70 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/sanity.cli.ts +29 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/sanity.config.ts +116 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/index.ts +6 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/reusable/blog-content.ts +89 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/reusable/description.ts +29 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/reusable/gated-content.ts +178 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/reusable/hero.ts +28 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/reusable/rich-text-block.ts +39 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/singleton/content-collection.ts +45 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/content/author.ts +70 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/content/blog-category.ts +55 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/index.ts +134 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/layout/company-data.ts +70 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/layout/footer.ts +79 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/layout/navbar.ts +75 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/layout/redirect.ts +152 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/layout/structured-data.ts +124 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/link.ts +125 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/logo-field.ts +9 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/metadata.ts +132 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/nav-objects.ts +192 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/page-builder.ts +43 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/page-folder.ts +124 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/page.ts +265 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/richText.ts +63 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/structured-data-blocks.ts +436 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/singletons.ts +51 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/structure.ts +334 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/tags.ts +89 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/utils/image.ts +8 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/utils/link.ts +140 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/utils/page-builder-markdown.ts +113 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/utils/page-json-ld.ts +208 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/scripts/sanity-typegen.ts +36 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/utils/check-redirect.ts +30 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/utils/json-ld.tsx +522 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/utils/metadata.ts +189 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/utils/rss.ts +96 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/utils/sitemap.ts +58 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/proxy.ts +90 -0
- package/src/modules/features/env/dotenvx/files/vercelignore +5 -0
- package/src/templates/next-default/.env.example +2 -2
- package/src/templates/next-default/.github/workflows/ci.yml +1 -1
- package/src/templates/next-default/.vscode/settings.json +0 -2
- package/src/templates/next-default/components/ui/image/index.tsx +2 -4
- package/src/templates/next-default/knip.json +1 -1
- package/src/templates/next-default/lefthook.yml +1 -1
- package/src/templates/next-default/lib/utils/README.md +18 -5
- package/src/templates/next-default/lib/utils/image-sizes.ts +72 -0
- package/src/templates/next-default/lib/utils/merge-refs.ts +56 -0
- package/src/templates/next-default/lib/utils/strings.ts +1 -51
- package/src/templates/next-default/next.config.ts +2 -3
- package/src/templates/next-default/package.json +2 -5
- package/src/templates/next-experiments/.env.example +2 -2
- package/src/templates/next-experiments/.github/workflows/ci.yml +1 -1
- package/src/templates/next-experiments/.vscode/settings.json +0 -2
- package/src/templates/next-experiments/app/page.tsx +1 -1
- package/src/templates/next-experiments/components/ui/image/index.tsx +2 -4
- package/src/templates/next-experiments/knip.json +1 -1
- package/src/templates/next-experiments/lefthook.yml +1 -1
- package/src/templates/next-experiments/lib/utils/README.md +18 -5
- package/src/templates/next-experiments/lib/utils/image-sizes.ts +72 -0
- package/src/templates/next-experiments/lib/utils/merge-refs.ts +56 -0
- package/src/templates/next-experiments/lib/utils/strings.ts +1 -51
- package/src/templates/next-experiments/next.config.ts +2 -3
- package/src/templates/next-experiments/package.json +2 -5
- package/src/templates/next-pagebuilder/.env.example +1 -0
- package/src/templates/next-pagebuilder/.github/workflows/ci.yml +1 -1
- package/src/templates/next-pagebuilder/.vscode/settings.json +0 -2
- package/src/templates/next-pagebuilder/README.md +64 -0
- package/src/templates/next-pagebuilder/app/sitemap.xml/route.ts +3 -1
- package/src/templates/next-pagebuilder/biome.json +2 -1
- package/src/templates/next-pagebuilder/components/layout/footer/index.tsx +3 -6
- package/src/templates/next-pagebuilder/components/layout/header/index.tsx +3 -2
- package/src/templates/next-pagebuilder/components/layout/json-ld/index.tsx +3 -8
- package/src/templates/next-pagebuilder/components/page-builder/components/content-collection/content-filters.tsx +23 -40
- package/src/templates/next-pagebuilder/components/page-builder/components/content-collection/index.tsx +88 -58
- package/src/templates/next-pagebuilder/components/page-builder/components/content-collection/skeleton.tsx +4 -16
- package/src/templates/next-pagebuilder/components/page-builder/components/content-collection/types.ts +14 -1
- package/src/templates/next-pagebuilder/components/page-builder/components/rich-text-block.tsx +16 -0
- package/src/templates/next-pagebuilder/components/page-builder/renderer.tsx +21 -7
- package/src/templates/next-pagebuilder/components/page-document/index.tsx +1 -0
- package/src/templates/next-pagebuilder/components/ui/image/index.tsx +2 -1
- package/src/templates/next-pagebuilder/knip.json +3 -2
- package/src/templates/next-pagebuilder/lefthook.yml +1 -1
- package/src/templates/next-pagebuilder/lib/integrations/sanity/sanity.types.ts +1221 -977
- package/src/templates/next-pagebuilder/lib/utils/image-sizes.ts +72 -0
- package/src/templates/next-pagebuilder/lib/utils/merge-refs.ts +56 -0
- package/src/templates/next-pagebuilder/next.config.ts +2 -3
- package/src/templates/next-pagebuilder/package.json +4 -6
- package/src/templates/next-pagebuilder-cache/.biome/plugins/README.md +21 -0
- package/src/templates/next-pagebuilder-cache/.biome/plugins/no-anchor-element.grit +12 -0
- package/src/templates/next-pagebuilder-cache/.biome/plugins/no-relative-parent-imports.grit +10 -0
- package/src/templates/next-pagebuilder-cache/.biome/plugins/no-unnecessary-forwardref.grit +9 -0
- package/src/templates/next-pagebuilder-cache/.env.example +40 -0
- package/src/templates/next-pagebuilder-cache/.github/PULL_REQUEST_TEMPLATE.md +13 -0
- package/src/templates/next-pagebuilder-cache/.github/workflows/ci.yml +105 -0
- package/src/templates/next-pagebuilder-cache/.vscode/extensions.json +19 -0
- package/src/templates/next-pagebuilder-cache/.vscode/settings.json +103 -0
- package/src/templates/next-pagebuilder-cache/.zed/settings.json +47 -0
- package/src/templates/next-pagebuilder-cache/README.md +212 -0
- package/src/templates/next-pagebuilder-cache/_gitignore +65 -0
- package/src/templates/next-pagebuilder-cache/app/(content)/[[...slug]]/page.tsx +124 -0
- package/src/templates/next-pagebuilder-cache/app/(content)/layout.tsx +28 -0
- package/src/templates/next-pagebuilder-cache/app/actions/unlock-gated-content.ts +53 -0
- package/src/templates/next-pagebuilder-cache/app/favicon.ico +0 -0
- package/src/templates/next-pagebuilder-cache/app/layout.tsx +83 -0
- package/src/templates/next-pagebuilder-cache/app/robots.ts +13 -0
- package/src/templates/next-pagebuilder-cache/app/sitemap.xml/route.ts +84 -0
- package/src/templates/next-pagebuilder-cache/biome.json +257 -0
- package/src/templates/next-pagebuilder-cache/components/layout/footer/index.tsx +127 -0
- package/src/templates/next-pagebuilder-cache/components/layout/header/components/cta-button.tsx +28 -0
- package/src/templates/next-pagebuilder-cache/components/layout/header/components/mega-menu-panel.tsx +90 -0
- package/src/templates/next-pagebuilder-cache/components/layout/header/components/nav-item-renderer.tsx +98 -0
- package/src/templates/next-pagebuilder-cache/components/layout/header/components/nav-leaf-item.tsx +33 -0
- package/src/templates/next-pagebuilder-cache/components/layout/header/components/types.ts +7 -0
- package/src/templates/next-pagebuilder-cache/components/layout/header/header-client.tsx +110 -0
- package/src/templates/next-pagebuilder-cache/components/layout/header/index.tsx +32 -0
- package/src/templates/next-pagebuilder-cache/components/layout/json-ld/index.tsx +79 -0
- package/src/templates/next-pagebuilder-cache/components/layout/wrapper/index.tsx +33 -0
- package/src/templates/next-pagebuilder-cache/components/page-builder/components/article-content/index.tsx +78 -0
- package/src/templates/next-pagebuilder-cache/components/page-builder/components/article-content/related-post-item.tsx +27 -0
- package/src/templates/next-pagebuilder-cache/components/page-builder/components/content-collection/content-card.tsx +66 -0
- package/src/templates/next-pagebuilder-cache/components/page-builder/components/content-collection/content-filters.tsx +76 -0
- package/src/templates/next-pagebuilder-cache/components/page-builder/components/content-collection/content-grid.tsx +40 -0
- package/src/templates/next-pagebuilder-cache/components/page-builder/components/content-collection/content-pagination-nav.tsx +71 -0
- package/src/templates/next-pagebuilder-cache/components/page-builder/components/content-collection/index.tsx +306 -0
- package/src/templates/next-pagebuilder-cache/components/page-builder/components/content-collection/skeleton.tsx +41 -0
- package/src/templates/next-pagebuilder-cache/components/page-builder/components/content-collection/types.ts +32 -0
- package/src/templates/next-pagebuilder-cache/components/page-builder/components/description.tsx +17 -0
- package/src/templates/next-pagebuilder-cache/components/page-builder/components/gated-content/index.tsx +169 -0
- package/src/templates/next-pagebuilder-cache/components/page-builder/components/hero.tsx +17 -0
- package/src/templates/next-pagebuilder-cache/components/page-builder/components/rich-text-block.tsx +16 -0
- package/src/templates/next-pagebuilder-cache/components/page-builder/renderer.tsx +71 -0
- package/src/templates/next-pagebuilder-cache/components/page-builder/types.ts +25 -0
- package/src/templates/next-pagebuilder-cache/components/page-document/index.tsx +147 -0
- package/src/templates/next-pagebuilder-cache/components/ui/image/index.tsx +216 -0
- package/src/templates/next-pagebuilder-cache/components/ui/link/index.tsx +152 -0
- package/src/templates/next-pagebuilder-cache/components.json +24 -0
- package/src/templates/next-pagebuilder-cache/knip.json +35 -0
- package/src/templates/next-pagebuilder-cache/lefthook.yml +25 -0
- package/src/templates/next-pagebuilder-cache/lib/integrations/sanity/sanity.types.ts +1588 -0
- package/src/templates/next-pagebuilder-cache/lib/scripts/setup-ci-secrets.ts +117 -0
- package/src/templates/next-pagebuilder-cache/lib/styles/cn.ts +5 -0
- package/src/templates/next-pagebuilder-cache/lib/styles/global.css +69 -0
- package/src/templates/next-pagebuilder-cache/lib/utils/base-url.ts +17 -0
- package/src/templates/next-pagebuilder-cache/lib/utils/format-date.ts +8 -0
- package/src/templates/next-pagebuilder-cache/lib/utils/slug-tag.ts +6 -0
- package/src/templates/next-pagebuilder-cache/next.config.ts +176 -0
- package/src/templates/next-pagebuilder-cache/package.json +62 -0
- package/src/templates/next-pagebuilder-cache/postcss.config.mjs +65 -0
- package/src/templates/next-pagebuilder-cache/svg.d.ts +6 -0
- package/src/templates/next-pagebuilder-cache/tsconfig.json +38 -0
- package/src/templates/next-webgl/.env.example +2 -2
- package/src/templates/next-webgl/.github/workflows/ci.yml +1 -1
- package/src/templates/next-webgl/.vscode/settings.json +0 -2
- package/src/templates/next-webgl/components/ui/image/index.tsx +2 -4
- package/src/templates/next-webgl/knip.json +1 -1
- package/src/templates/next-webgl/lefthook.yml +1 -1
- package/src/templates/next-webgl/lib/utils/README.md +18 -5
- package/src/templates/next-webgl/lib/utils/image-sizes.ts +72 -0
- package/src/templates/next-webgl/lib/utils/merge-refs.ts +56 -0
- package/src/templates/next-webgl/lib/utils/strings.ts +1 -51
- package/src/templates/next-webgl/next.config.ts +2 -3
- package/src/templates/next-webgl/package.json +4 -7
- package/src/templates/next-experiments/tsconfig.tsbuildinfo +0 -1
- package/src/templates/next-pagebuilder/app/actions/refresh.ts +0 -5
- package/src/templates/next-webgl/tsconfig.tsbuildinfo +0 -1
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
export interface RssFeedItem {
|
|
2
|
+
title: string;
|
|
3
|
+
link: string;
|
|
4
|
+
description?: string | undefined;
|
|
5
|
+
pubDate?: string | undefined;
|
|
6
|
+
authors?: string[] | undefined;
|
|
7
|
+
category?: string | undefined;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface RssFeedConfig {
|
|
11
|
+
title: string;
|
|
12
|
+
link: string;
|
|
13
|
+
description: string;
|
|
14
|
+
feedUrl: string;
|
|
15
|
+
language?: string;
|
|
16
|
+
items: RssFeedItem[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function escapeXml(str: string): string {
|
|
20
|
+
return str
|
|
21
|
+
.replace(/&/g, "&")
|
|
22
|
+
.replace(/</g, "<")
|
|
23
|
+
.replace(/>/g, ">")
|
|
24
|
+
.replace(/"/g, """)
|
|
25
|
+
.replace(/'/g, "'");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function generateRssFeed(config: RssFeedConfig): Response {
|
|
29
|
+
const {
|
|
30
|
+
title,
|
|
31
|
+
link,
|
|
32
|
+
description,
|
|
33
|
+
feedUrl,
|
|
34
|
+
language = "en-us",
|
|
35
|
+
items,
|
|
36
|
+
} = config;
|
|
37
|
+
|
|
38
|
+
const itemsXml = items
|
|
39
|
+
.map((item) => {
|
|
40
|
+
const lines = [
|
|
41
|
+
` <title>${escapeXml(item.title)}</title>`,
|
|
42
|
+
` <link>${escapeXml(item.link)}</link>`,
|
|
43
|
+
` <guid isPermaLink="true">${escapeXml(item.link)}</guid>`,
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
if (item.description) {
|
|
47
|
+
lines.push(
|
|
48
|
+
` <description>${escapeXml(item.description)}</description>`,
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const pubDate = item.pubDate ? new Date(item.pubDate) : null;
|
|
53
|
+
if (pubDate && !Number.isNaN(pubDate.getTime())) {
|
|
54
|
+
lines.push(` <pubDate>${pubDate.toUTCString()}</pubDate>`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
for (const author of item.authors ?? []) {
|
|
58
|
+
lines.push(` <dc:creator>${escapeXml(author)}</dc:creator>`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (item.category) {
|
|
62
|
+
lines.push(` <category>${escapeXml(item.category)}</category>`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return ` <item>\n${lines.join("\n")}\n </item>`;
|
|
66
|
+
})
|
|
67
|
+
.join("\n");
|
|
68
|
+
|
|
69
|
+
const xml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
70
|
+
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom">
|
|
71
|
+
<channel>
|
|
72
|
+
<title>${escapeXml(title)}</title>
|
|
73
|
+
<link>${escapeXml(link)}</link>
|
|
74
|
+
<description>${escapeXml(description)}</description>
|
|
75
|
+
<language>${language}</language>
|
|
76
|
+
<atom:link href="${escapeXml(feedUrl)}" rel="self" type="application/rss+xml" />
|
|
77
|
+
${itemsXml}
|
|
78
|
+
</channel>
|
|
79
|
+
</rss>`;
|
|
80
|
+
|
|
81
|
+
return new Response(xml, {
|
|
82
|
+
headers: {
|
|
83
|
+
"Content-Type": "application/rss+xml",
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function rssErrorResponse(): Response {
|
|
89
|
+
return new Response(
|
|
90
|
+
'<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"><channel><title>Error</title></channel></rss>',
|
|
91
|
+
{
|
|
92
|
+
status: 500,
|
|
93
|
+
headers: { "Content-Type": "application/xml" },
|
|
94
|
+
},
|
|
95
|
+
);
|
|
96
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { cacheLife, cacheTag } from "next/cache";
|
|
2
|
+
import { PINNED_PAGE_ENTRIES } from "@/lib/integrations/sanity/pinned-pages";
|
|
3
|
+
import { PAGE_SITEMAP_QUERY } from "@/lib/integrations/sanity/queries";
|
|
4
|
+
import type { PAGE_SITEMAP_QUERY_RESULT } from "@/lib/integrations/sanity/sanity.types";
|
|
5
|
+
import { SITEMAP_TAG } from "@/lib/integrations/sanity/tags";
|
|
6
|
+
import { getBaseUrl } from "./base-url";
|
|
7
|
+
|
|
8
|
+
export const getSitemapBaseUrl = getBaseUrl;
|
|
9
|
+
|
|
10
|
+
const getNormalizedPageSlug = (slug: string | null) => {
|
|
11
|
+
if (!slug || slug === "/") return "";
|
|
12
|
+
return slug.replace(/^\/+|\/+$/g, "");
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const getPagePath = (slug: string | null) => {
|
|
16
|
+
const normalizedSlug = getNormalizedPageSlug(slug);
|
|
17
|
+
return normalizedSlug ? `/${normalizedSlug}` : "/";
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const getPageUrl = (baseUrl: string, slug: string | null) =>
|
|
21
|
+
new URL(getPagePath(slug), baseUrl).toString();
|
|
22
|
+
|
|
23
|
+
export const getMarkdownPath = (baseUrl: string, slug: string | null) => {
|
|
24
|
+
const normalizedSlug = getNormalizedPageSlug(slug);
|
|
25
|
+
|
|
26
|
+
return normalizedSlug
|
|
27
|
+
? `${baseUrl}/${normalizedSlug}.md`
|
|
28
|
+
: `${baseUrl}/index.md`;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// Cached and tagged, not per-request: robots.txt points every crawler here, so an
|
|
32
|
+
// uncached read is a Sanity round trip per crawl hit.
|
|
33
|
+
export const getSitemap = async () => {
|
|
34
|
+
"use cache";
|
|
35
|
+
const sanityModule = await import("@/lib/integrations/sanity/client");
|
|
36
|
+
const client = sanityModule?.client;
|
|
37
|
+
|
|
38
|
+
if (!client) return null;
|
|
39
|
+
|
|
40
|
+
const pages = await client
|
|
41
|
+
.fetch<PAGE_SITEMAP_QUERY_RESULT>(PAGE_SITEMAP_QUERY, {
|
|
42
|
+
pinnedIds: PINNED_PAGE_ENTRIES.map((entry) => entry.id),
|
|
43
|
+
})
|
|
44
|
+
.catch((error) => {
|
|
45
|
+
console.error("Error fetching the sitemap:", error);
|
|
46
|
+
return null;
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
if (pages) {
|
|
50
|
+
cacheTag(SITEMAP_TAG);
|
|
51
|
+
} else {
|
|
52
|
+
// No publish clears a failed read, and this prerenders — an outage at build
|
|
53
|
+
// time would otherwise bake in an empty sitemap for the full ~1y cacheLife.
|
|
54
|
+
cacheLife("hours");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return pages;
|
|
58
|
+
};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { NextRequest } from "next/server";
|
|
2
|
+
import { NextResponse } from "next/server";
|
|
3
|
+
import {
|
|
4
|
+
acceptHeaderRoutes,
|
|
5
|
+
ignoredPaths,
|
|
6
|
+
mdExtensionRoutes,
|
|
7
|
+
} from "@/lib/integrations/sanity/markdown-proxy.config";
|
|
8
|
+
|
|
9
|
+
export function proxy(request: NextRequest) {
|
|
10
|
+
const { pathname } = request.nextUrl;
|
|
11
|
+
const acceptHeader = request.headers.get("accept") || "";
|
|
12
|
+
|
|
13
|
+
if (ignoredPaths.some((p) => pathname.startsWith(p))) {
|
|
14
|
+
return NextResponse.next();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Collapse repeated slashes before any rewrite, or the rewrite below would
|
|
18
|
+
// mask the alias (e.g. /foo//bar.md silently resolving to /foo/bar.md).
|
|
19
|
+
const collapsedPathname = pathname.replace(/\/{2,}/g, "/");
|
|
20
|
+
if (collapsedPathname !== pathname) {
|
|
21
|
+
const url = request.nextUrl.clone();
|
|
22
|
+
url.pathname = collapsedPathname;
|
|
23
|
+
return NextResponse.redirect(url, 308);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (pathname === "/" && acceptHeader.includes("text/markdown")) {
|
|
27
|
+
const url = request.nextUrl.clone();
|
|
28
|
+
url.pathname = "/api/index.md";
|
|
29
|
+
return NextResponse.rewrite(url);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// 1. Rewrite .md extension URLs to API routes
|
|
33
|
+
// e.g. /about.md → /api/about.md
|
|
34
|
+
for (const route of mdExtensionRoutes) {
|
|
35
|
+
const match = route.regex.exec(pathname);
|
|
36
|
+
const slug = match?.[1];
|
|
37
|
+
if (slug) {
|
|
38
|
+
const url = request.nextUrl.clone();
|
|
39
|
+
url.pathname = route.apiPath.replace("[slug]", slug);
|
|
40
|
+
return NextResponse.rewrite(url);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// 2. Accept: text/markdown — rewrite to markdown API route
|
|
45
|
+
if (acceptHeader.includes("text/markdown")) {
|
|
46
|
+
for (const route of acceptHeaderRoutes) {
|
|
47
|
+
const match = route.regex.exec(pathname);
|
|
48
|
+
const slug = match?.[1];
|
|
49
|
+
if (slug) {
|
|
50
|
+
const url = request.nextUrl.clone();
|
|
51
|
+
url.pathname = route.apiPath.replace("[slug]", slug);
|
|
52
|
+
return NextResponse.rewrite(url);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// 3. Add Link header for markdown discoverability on HTML page requests
|
|
58
|
+
const response = NextResponse.next();
|
|
59
|
+
|
|
60
|
+
if (pathname === "/") {
|
|
61
|
+
response.headers.set(
|
|
62
|
+
"Link",
|
|
63
|
+
'</index.md>; rel="alternate"; type="text/markdown"',
|
|
64
|
+
);
|
|
65
|
+
response.headers.set("Vary", "Accept");
|
|
66
|
+
return response;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
for (const route of acceptHeaderRoutes) {
|
|
70
|
+
const match = route.regex.exec(pathname);
|
|
71
|
+
const slug = match?.[1];
|
|
72
|
+
if (slug) {
|
|
73
|
+
const mdUrl = route.publicPath.replace("[slug]", slug);
|
|
74
|
+
response.headers.set(
|
|
75
|
+
"Link",
|
|
76
|
+
`<${mdUrl}>; rel="alternate"; type="text/markdown"`,
|
|
77
|
+
);
|
|
78
|
+
response.headers.set("Vary", "Accept");
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return response;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export const config = {
|
|
87
|
+
matcher: [
|
|
88
|
+
"/((?!_next/static|_next/image|favicon.ico|sitemap.xml|sitemap.md|robots.txt).*)",
|
|
89
|
+
],
|
|
90
|
+
};
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
# Never upload the private keys or local env files on `vercel deploy`.
|
|
2
2
|
# (Encrypted .env / .env.production / .env.preview ARE uploaded — they're safe.)
|
|
3
|
+
# Patterns stay unanchored so nested app/package dirs are covered too.
|
|
3
4
|
.env.keys
|
|
4
5
|
.env*.local
|
|
6
|
+
# Exception: `vercel pull` writes its own env under .vercel/, and Next traces it
|
|
7
|
+
# into the function bundles — stripping it breaks `deploy --prebuilt` with an
|
|
8
|
+
# ENOENT on the remote build.
|
|
9
|
+
!/.vercel/*.local
|
|
@@ -56,8 +56,8 @@ NEXT_PUBLIC_SANITY_API_READ_TOKEN=""
|
|
|
56
56
|
# Optional - for server-side mutations and visual editing
|
|
57
57
|
# SANITY_PRIVATE_TOKEN=""
|
|
58
58
|
|
|
59
|
-
# Optional - API version (defaults to
|
|
60
|
-
# NEXT_PUBLIC_SANITY_API_VERSION="
|
|
59
|
+
# Optional - API version (defaults to 2025-02-19, the floor for Content Releases)
|
|
60
|
+
# NEXT_PUBLIC_SANITY_API_VERSION="2025-02-19"
|
|
61
61
|
|
|
62
62
|
|
|
63
63
|
# ============================================
|
|
@@ -58,7 +58,6 @@
|
|
|
58
58
|
|
|
59
59
|
// General
|
|
60
60
|
"files.eol": "\n",
|
|
61
|
-
// "typescript.experimental.useTsgo": true, // Disabled: tsgo doesn't support Next.js plugin
|
|
62
61
|
"javascript.suggest.autoImports": true,
|
|
63
62
|
// Disable Prettier to prevent silent formatter conflicts
|
|
64
63
|
"prettier.enable": false,
|
|
@@ -90,7 +89,6 @@
|
|
|
90
89
|
|
|
91
90
|
// TypeScript
|
|
92
91
|
"typescript.suggest.autoImports": true,
|
|
93
|
-
"typescript.tsdk": "node_modules/typescript/lib",
|
|
94
92
|
|
|
95
93
|
// Editor labels (fixed for .tsx files)
|
|
96
94
|
"workbench.editor.customLabels.patterns": {
|
|
@@ -9,8 +9,7 @@
|
|
|
9
9
|
import cn from "clsx"
|
|
10
10
|
import NextImage, { type ImageProps as NextImageProps } from "next/image"
|
|
11
11
|
import type { CSSProperties, Ref } from "react"
|
|
12
|
-
|
|
13
|
-
const DESKTOP_BREAKPOINT = 1440
|
|
12
|
+
import { buildImageSizes } from "@/lib/utils/image-sizes"
|
|
14
13
|
|
|
15
14
|
/**
|
|
16
15
|
* Enhanced Image component props extending Next.js Image.
|
|
@@ -167,8 +166,7 @@ export function Image({
|
|
|
167
166
|
}: ImageProps) {
|
|
168
167
|
// Generate responsive sizes if not provided
|
|
169
168
|
const finalSizes =
|
|
170
|
-
sizes ||
|
|
171
|
-
`(max-width: ${DESKTOP_BREAKPOINT}px) ${mobileSize}, ${desktopSize}`
|
|
169
|
+
sizes || buildImageSizes({ base: mobileSize, desktop: desktopSize })
|
|
172
170
|
|
|
173
171
|
// Early return after hooks
|
|
174
172
|
if (!src) return null
|
|
@@ -5,10 +5,12 @@ Pure utility functions organized by concern.
|
|
|
5
5
|
Use explicit imports for clarity and better tree-shaking:
|
|
6
6
|
|
|
7
7
|
```tsx
|
|
8
|
-
import { clamp, lerp, mapRange } from '@/utils/math'
|
|
9
|
-
import { slugify } from '@/utils/strings'
|
|
10
|
-
import { generatePageMetadata } from '@/utils/metadata'
|
|
11
|
-
import { tovw, torem, toem } from '@/utils/viewport'
|
|
8
|
+
import { clamp, lerp, mapRange } from '@/lib/utils/math'
|
|
9
|
+
import { slugify } from '@/lib/utils/strings'
|
|
10
|
+
import { generatePageMetadata } from '@/lib/utils/metadata'
|
|
11
|
+
import { tovw, torem, toem } from '@/lib/utils/viewport'
|
|
12
|
+
import { buildImageSizes, buildGridImageSizes } from '@/lib/utils/image-sizes'
|
|
13
|
+
import { useMergeRefs } from '@/lib/utils/merge-refs'
|
|
12
14
|
```
|
|
13
15
|
|
|
14
16
|
## Modules
|
|
@@ -18,8 +20,10 @@ import { tovw, torem, toem } from '@/utils/viewport'
|
|
|
18
20
|
| `math` | `clamp`, `lerp`, `mapRange`, `modulo`, `normalize`, `distance` |
|
|
19
21
|
| `easings` | All easing functions (`easeOutCubic`, etc.) |
|
|
20
22
|
| `viewport` | `tovw`, `torem`, `toem` |
|
|
21
|
-
| `strings` | `slugify`, `
|
|
23
|
+
| `strings` | `slugify`, `capitalizeFirstLetter`, `twoDigits`, `numberWithCommas` |
|
|
22
24
|
| `metadata` | `generatePageMetadata` |
|
|
25
|
+
| `image-sizes` | `buildImageSizes`, `buildGridImageSizes` |
|
|
26
|
+
| `merge-refs` | `mergeRefs`, `useMergeRefs`, `assignRef` |
|
|
23
27
|
|
|
24
28
|
## Common Patterns
|
|
25
29
|
|
|
@@ -37,4 +41,13 @@ toem(16, 14) // → "1.14em"
|
|
|
37
41
|
|
|
38
42
|
// SEO
|
|
39
43
|
export const metadata = generatePageMetadata({ title: 'About' })
|
|
44
|
+
|
|
45
|
+
// next/image sizes — keys mirror the element's Tailwind variants
|
|
46
|
+
buildImageSizes({ base: '100vw', desktop: '796px' })
|
|
47
|
+
buildGridImageSizes({ base: 1, tablet: 2, desktop: 3 })
|
|
48
|
+
|
|
49
|
+
// Refs — in a component, use the hook: an inline mergeRefs([...]) is a new
|
|
50
|
+
// callback each render, so React detaches and reattaches every ref.
|
|
51
|
+
const mergedRef = useMergeRefs([localRef, ref])
|
|
52
|
+
return <input ref={mergedRef} />
|
|
40
53
|
```
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/** Mirrors the theme's `--breakpoint-*` scale, which clears Tailwind's defaults. */
|
|
2
|
+
export const BASEMENT_BREAKPOINTS = {
|
|
3
|
+
mobile: 640,
|
|
4
|
+
tablet: 768,
|
|
5
|
+
"tablet-lg": 1024,
|
|
6
|
+
desktop: 1440,
|
|
7
|
+
"desktop-large": 1920,
|
|
8
|
+
} as const
|
|
9
|
+
|
|
10
|
+
export type BreakpointMap = Readonly<Record<string, number>>
|
|
11
|
+
|
|
12
|
+
/** Mirrors the element's variants: `grid-cols-1 tablet:grid-cols-2` → `{ base: 1, tablet: 2 }`. */
|
|
13
|
+
export type ByBreakpoint<
|
|
14
|
+
T,
|
|
15
|
+
B extends BreakpointMap = typeof BASEMENT_BREAKPOINTS,
|
|
16
|
+
> = { base: T } & Partial<Record<Extract<keyof B, string>, T>>
|
|
17
|
+
|
|
18
|
+
function columnWidth(columns: number): string {
|
|
19
|
+
return `${Math.round(100 / columns)}vw`
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Widest first, since `sizes` takes the first matching condition and the last
|
|
24
|
+
* entry must be bare. `min-width` matches the Tailwind variant's boundary; a
|
|
25
|
+
* `max-width: <next> - 1` one would leave a fractional-pixel gap.
|
|
26
|
+
*/
|
|
27
|
+
function toSizes(
|
|
28
|
+
base: string,
|
|
29
|
+
overrides: Readonly<Record<string, string | undefined>>,
|
|
30
|
+
scale: BreakpointMap
|
|
31
|
+
): string {
|
|
32
|
+
const conditions: string[] = []
|
|
33
|
+
|
|
34
|
+
for (const [name, min] of Object.entries(scale).sort(
|
|
35
|
+
([, a], [, b]) => b - a
|
|
36
|
+
)) {
|
|
37
|
+
const value = overrides[name]
|
|
38
|
+
if (value !== undefined) {
|
|
39
|
+
conditions.push(`(min-width: ${min}px) ${value}`)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return [...conditions, base].join(", ")
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Builds `sizes` from the CSS length the image renders at per breakpoint. */
|
|
47
|
+
export function buildImageSizes<
|
|
48
|
+
B extends BreakpointMap = typeof BASEMENT_BREAKPOINTS,
|
|
49
|
+
>(widths: ByBreakpoint<string, B>, breakpoints?: B): string {
|
|
50
|
+
const { base, ...overrides } = widths
|
|
51
|
+
return toSizes(base, overrides, breakpoints ?? BASEMENT_BREAKPOINTS)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Builds `sizes` from a grid's column count per breakpoint. Rounded: `sizes`
|
|
56
|
+
* only steers which srcset candidate the browser picks, and gaps make the real
|
|
57
|
+
* column narrower than `100 / N` anyway.
|
|
58
|
+
*/
|
|
59
|
+
export function buildGridImageSizes<
|
|
60
|
+
B extends BreakpointMap = typeof BASEMENT_BREAKPOINTS,
|
|
61
|
+
>(columns: ByBreakpoint<number, B>, breakpoints?: B): string {
|
|
62
|
+
const { base, ...overrides } = columns
|
|
63
|
+
const widths: Record<string, string> = {}
|
|
64
|
+
|
|
65
|
+
for (const [name, count] of Object.entries(overrides)) {
|
|
66
|
+
if (count !== undefined) {
|
|
67
|
+
widths[name] = columnWidth(count)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return toSizes(columnWidth(base), widths, breakpoints ?? BASEMENT_BREAKPOINTS)
|
|
72
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { type Ref, type RefCallback, useRef } from "react"
|
|
2
|
+
|
|
3
|
+
/** Returns the ref's own cleanup callback when it provides one (React 19). */
|
|
4
|
+
export function assignRef<T>(
|
|
5
|
+
ref: Ref<T> | undefined,
|
|
6
|
+
value: T | null
|
|
7
|
+
): ReturnType<RefCallback<T>> {
|
|
8
|
+
if (typeof ref === "function") {
|
|
9
|
+
return ref(value)
|
|
10
|
+
}
|
|
11
|
+
if (ref) {
|
|
12
|
+
ref.current = value
|
|
13
|
+
}
|
|
14
|
+
return undefined
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Merges object/callback refs into one callback ref, with React 19 ref cleanup. */
|
|
18
|
+
export function mergeRefs<T>(refs: (Ref<T> | undefined)[]): RefCallback<T> {
|
|
19
|
+
return (value) => {
|
|
20
|
+
const cleanups = refs.map((ref) => {
|
|
21
|
+
const cleanup = assignRef(ref, value)
|
|
22
|
+
return typeof cleanup === "function"
|
|
23
|
+
? cleanup
|
|
24
|
+
: () => assignRef(ref, null)
|
|
25
|
+
})
|
|
26
|
+
return () => {
|
|
27
|
+
for (const cleanup of cleanups) {
|
|
28
|
+
cleanup()
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Memoized {@link mergeRefs}, stable across renders for the same ref list.
|
|
36
|
+
* Compares the list here rather than passing it as `useMemo` deps: React only
|
|
37
|
+
* compares the common prefix, so a list that grows or shrinks would silently
|
|
38
|
+
* reuse the old callback and leave a ref unattached or stale.
|
|
39
|
+
*/
|
|
40
|
+
export function useMergeRefs<T>(refs: (Ref<T> | undefined)[]): RefCallback<T> {
|
|
41
|
+
const cache = useRef<{
|
|
42
|
+
refs: (Ref<T> | undefined)[]
|
|
43
|
+
merged: RefCallback<T>
|
|
44
|
+
} | null>(null)
|
|
45
|
+
|
|
46
|
+
if (
|
|
47
|
+
!cache.current ||
|
|
48
|
+
cache.current.refs.length !== refs.length ||
|
|
49
|
+
cache.current.refs.some((ref, i) => ref !== refs[i])
|
|
50
|
+
) {
|
|
51
|
+
// Snapshot: a caller may hand us a persistent array it mutates in place.
|
|
52
|
+
cache.current = { refs: [...refs], merged: mergeRefs(refs) }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return cache.current.merged
|
|
56
|
+
}
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* String & Object Utilities
|
|
3
3
|
*
|
|
4
|
-
* Helper functions for string manipulation
|
|
4
|
+
* Helper functions for string manipulation and object operations.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import type { Ref } from "react"
|
|
8
|
-
|
|
9
7
|
// =============================================================================
|
|
10
8
|
// STRING UTILITIES
|
|
11
9
|
// =============================================================================
|
|
@@ -196,51 +194,3 @@ export function iterableObject(
|
|
|
196
194
|
return value
|
|
197
195
|
})
|
|
198
196
|
}
|
|
199
|
-
|
|
200
|
-
// =============================================================================
|
|
201
|
-
// REF UTILITIES
|
|
202
|
-
// =============================================================================
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* Merges multiple React refs into a single ref callback.
|
|
206
|
-
*
|
|
207
|
-
* Useful when you need to assign multiple refs to the same element,
|
|
208
|
-
* such as combining a local ref with a forwarded ref.
|
|
209
|
-
*
|
|
210
|
-
* @param refs - Refs to merge (functions and ref objects supported)
|
|
211
|
-
* @returns A ref callback that updates all provided refs
|
|
212
|
-
*
|
|
213
|
-
* @example
|
|
214
|
-
* ```tsx
|
|
215
|
-
* // Component that needs both local and forwarded ref
|
|
216
|
-
* function MyComponent({ ref }: { ref: React.Ref<HTMLDivElement> }) {
|
|
217
|
-
* const localRef = useRef<HTMLDivElement>(null)
|
|
218
|
-
* const mergedRef = mergeRefs(localRef, ref)
|
|
219
|
-
*
|
|
220
|
-
* return <div ref={mergedRef}>Content</div>
|
|
221
|
-
* }
|
|
222
|
-
*
|
|
223
|
-
* // Forward ref pattern
|
|
224
|
-
* const ForwardedComponent = forwardRef(MyComponent)
|
|
225
|
-
* ```
|
|
226
|
-
*/
|
|
227
|
-
export function mergeRefs<T>(...refs: (Ref<T> | undefined)[]): Ref<T> {
|
|
228
|
-
return (value) => {
|
|
229
|
-
const cleanups = refs.reduce<VoidFunction[]>((accumulator, ref) => {
|
|
230
|
-
if (typeof ref === "function") {
|
|
231
|
-
const cleanup = ref(value)
|
|
232
|
-
if (typeof cleanup === "function") {
|
|
233
|
-
accumulator.push(cleanup)
|
|
234
|
-
}
|
|
235
|
-
} else if (ref) {
|
|
236
|
-
ref.current = value
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
return accumulator
|
|
240
|
-
}, [])
|
|
241
|
-
|
|
242
|
-
return () => {
|
|
243
|
-
for (const cleanup of cleanups) cleanup()
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
}
|
|
@@ -5,9 +5,8 @@ const nextConfig: NextConfig = {
|
|
|
5
5
|
reactStrictMode: true,
|
|
6
6
|
reactCompiler: true,
|
|
7
7
|
typedRoutes: true,
|
|
8
|
-
// Bundle the committed encrypted env files
|
|
9
|
-
//
|
|
10
|
-
// (and is git/vercel-ignored); missing files are skipped.
|
|
8
|
+
// Bundle the committed encrypted env files so instrumentation.ts can decrypt
|
|
9
|
+
// them at runtime. `.env.keys` is never listed; missing files are skipped.
|
|
11
10
|
outputFileTracingIncludes: {
|
|
12
11
|
"/**": ["./.env", "./.env.production", "./.env.preview"],
|
|
13
12
|
},
|
|
@@ -22,9 +22,7 @@
|
|
|
22
22
|
"lint:fix": "biome check --write --unsafe --max-diagnostics=200",
|
|
23
23
|
"prepare": "lefthook install",
|
|
24
24
|
"start": "next start",
|
|
25
|
-
"typecheck": "
|
|
26
|
-
"typecheck:compare": "bun run typecheck:tsc && bun run typecheck",
|
|
27
|
-
"typecheck:tsc": "tsc --noEmit --incremental false",
|
|
25
|
+
"typecheck": "tsc --noEmit",
|
|
28
26
|
"update:packages": "cross-env NPM_CHECK_INSTALLER=bun bunx npm-check -u"
|
|
29
27
|
},
|
|
30
28
|
"dependencies": {
|
|
@@ -45,7 +43,6 @@
|
|
|
45
43
|
"@types/node": "^24.13.1",
|
|
46
44
|
"@types/react": "^19.2.14",
|
|
47
45
|
"@types/react-dom": "^19.2.3",
|
|
48
|
-
"@typescript/native-preview": "^7.0.0-dev.20260323.1",
|
|
49
46
|
"babel-plugin-react-compiler": "^1.0.0",
|
|
50
47
|
"cross-env": "^10.1.0",
|
|
51
48
|
"knip": "^6.16.1",
|
|
@@ -53,7 +50,7 @@
|
|
|
53
50
|
"postcss-preset-env": "^10.6.1",
|
|
54
51
|
"schema-dts": "^2.0.0",
|
|
55
52
|
"tailwindcss": "^4.2.2",
|
|
56
|
-
"typescript": "^
|
|
53
|
+
"typescript": "^7.0.2"
|
|
57
54
|
},
|
|
58
55
|
"trustedDependencies": [
|
|
59
56
|
"@biomejs/biome",
|
|
@@ -56,8 +56,8 @@ NEXT_PUBLIC_SANITY_API_READ_TOKEN=""
|
|
|
56
56
|
# Optional - for server-side mutations and visual editing
|
|
57
57
|
# SANITY_PRIVATE_TOKEN=""
|
|
58
58
|
|
|
59
|
-
# Optional - API version (defaults to
|
|
60
|
-
# NEXT_PUBLIC_SANITY_API_VERSION="
|
|
59
|
+
# Optional - API version (defaults to 2025-02-19, the floor for Content Releases)
|
|
60
|
+
# NEXT_PUBLIC_SANITY_API_VERSION="2025-02-19"
|
|
61
61
|
|
|
62
62
|
|
|
63
63
|
# ============================================
|
|
@@ -58,7 +58,6 @@
|
|
|
58
58
|
|
|
59
59
|
// General
|
|
60
60
|
"files.eol": "\n",
|
|
61
|
-
// "typescript.experimental.useTsgo": true, // Disabled: tsgo doesn't support Next.js plugin
|
|
62
61
|
"javascript.suggest.autoImports": true,
|
|
63
62
|
// Disable Prettier to prevent silent formatter conflicts
|
|
64
63
|
"prettier.enable": false,
|
|
@@ -90,7 +89,6 @@
|
|
|
90
89
|
|
|
91
90
|
// TypeScript
|
|
92
91
|
"typescript.suggest.autoImports": true,
|
|
93
|
-
"typescript.tsdk": "node_modules/typescript/lib",
|
|
94
92
|
|
|
95
93
|
// Editor labels (fixed for .tsx files)
|
|
96
94
|
"workbench.editor.customLabels.patterns": {
|
|
@@ -62,7 +62,7 @@ const SCRIPTS = [
|
|
|
62
62
|
{ cmd: "bun dev:https", desc: "Start with HTTPS" },
|
|
63
63
|
{ cmd: "bun build", desc: "Build for production" },
|
|
64
64
|
{ cmd: "bun lint:fix", desc: "Fix lint issues" },
|
|
65
|
-
{ cmd: "bun typecheck", desc: "Type check with
|
|
65
|
+
{ cmd: "bun typecheck", desc: "Type check with tsc" },
|
|
66
66
|
]
|
|
67
67
|
|
|
68
68
|
export default function Home() {
|