bsmnt 0.9.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.
Files changed (344) hide show
  1. package/dist/application/add-integration/index.d.ts.map +1 -1
  2. package/dist/application/add-integration/index.js +19 -2
  3. package/dist/application/add-integration/index.js.map +1 -1
  4. package/dist/application/shared/node-version.js.map +1 -1
  5. package/dist/core/create/internal-ids.d.ts +3 -0
  6. package/dist/core/create/internal-ids.d.ts.map +1 -1
  7. package/dist/core/create/internal-ids.js +4 -0
  8. package/dist/core/create/internal-ids.js.map +1 -1
  9. package/dist/domain/agents.d.ts.map +1 -1
  10. package/dist/domain/animations.d.ts.map +1 -1
  11. package/dist/domain/cms.d.ts.map +1 -1
  12. package/dist/domain/forms.d.ts.map +1 -1
  13. package/dist/domain/hooks.d.ts.map +1 -1
  14. package/dist/domain/skills.d.ts.map +1 -1
  15. package/dist/domain/templates.d.ts +10 -0
  16. package/dist/domain/templates.d.ts.map +1 -1
  17. package/dist/domain/templates.js +5 -0
  18. package/dist/domain/templates.js.map +1 -1
  19. package/dist/index.js +18 -5
  20. package/dist/index.js.map +1 -1
  21. package/dist/infrastructure/agents/register-sanity-mcp.js +3 -8
  22. package/dist/infrastructure/agents/register-sanity-mcp.js.map +1 -1
  23. package/dist/infrastructure/agents/register-storybook-mcp.d.ts +18 -0
  24. package/dist/infrastructure/agents/register-storybook-mcp.d.ts.map +1 -0
  25. package/dist/infrastructure/agents/register-storybook-mcp.js +157 -0
  26. package/dist/infrastructure/agents/register-storybook-mcp.js.map +1 -0
  27. package/dist/infrastructure/create/lifecycle-registry.d.ts.map +1 -1
  28. package/dist/infrastructure/create/lifecycle-registry.js +39 -1
  29. package/dist/infrastructure/create/lifecycle-registry.js.map +1 -1
  30. package/dist/infrastructure/create/merge-registry.d.ts.map +1 -1
  31. package/dist/infrastructure/create/merge-registry.js +4 -0
  32. package/dist/infrastructure/create/merge-registry.js.map +1 -1
  33. package/dist/infrastructure/create/sanity-auth.js.map +1 -1
  34. package/dist/infrastructure/create/setup-agent.d.ts +6 -1
  35. package/dist/infrastructure/create/setup-agent.d.ts.map +1 -1
  36. package/dist/infrastructure/create/setup-agent.js +23 -21
  37. package/dist/infrastructure/create/setup-agent.js.map +1 -1
  38. package/dist/infrastructure/create/setup-dotenvx.js.map +1 -1
  39. package/dist/infrastructure/create/setup-remote.js.map +1 -1
  40. package/dist/infrastructure/create/setup-sanity.d.ts.map +1 -1
  41. package/dist/infrastructure/create/setup-sanity.js +12 -1
  42. package/dist/infrastructure/create/setup-sanity.js.map +1 -1
  43. package/dist/infrastructure/integrations/ensure-studio-headers.d.ts +6 -0
  44. package/dist/infrastructure/integrations/ensure-studio-headers.d.ts.map +1 -0
  45. package/dist/infrastructure/integrations/ensure-studio-headers.js +153 -0
  46. package/dist/infrastructure/integrations/ensure-studio-headers.js.map +1 -0
  47. package/dist/infrastructure/integrations/inject-integration.d.ts.map +1 -1
  48. package/dist/infrastructure/integrations/inject-integration.js +2 -0
  49. package/dist/infrastructure/integrations/inject-integration.js.map +1 -1
  50. package/dist/infrastructure/update/notifier.js.map +1 -1
  51. package/dist/modules/features/cms/index.d.ts.map +1 -1
  52. package/dist/modules/features/cms/index.js +2 -0
  53. package/dist/modules/features/cms/index.js.map +1 -1
  54. package/dist/modules/features/cms/sanity/config.d.ts.map +1 -1
  55. package/dist/modules/features/cms/sanity/config.js +3 -1
  56. package/dist/modules/features/cms/sanity/config.js.map +1 -1
  57. package/dist/modules/features/cms/sanity/index.d.ts.map +1 -1
  58. package/dist/modules/features/cms/sanity/index.js +10 -1
  59. package/dist/modules/features/cms/sanity/index.js.map +1 -1
  60. package/dist/modules/features/cms/sanity/mergers/check-integration.d.ts.map +1 -1
  61. package/dist/modules/features/cms/sanity/mergers/check-integration.js +15 -6
  62. package/dist/modules/features/cms/sanity/mergers/check-integration.js.map +1 -1
  63. package/dist/modules/features/cms/sanity/mergers/knip.d.ts.map +1 -1
  64. package/dist/modules/features/cms/sanity/mergers/knip.js +1 -0
  65. package/dist/modules/features/cms/sanity/mergers/knip.js.map +1 -1
  66. package/dist/modules/features/cms/sanity/mergers/layout.js.map +1 -1
  67. package/dist/modules/features/cms/sanity/mergers/package-json.d.ts +12 -0
  68. package/dist/modules/features/cms/sanity/mergers/package-json.d.ts.map +1 -0
  69. package/dist/modules/features/cms/sanity/mergers/package-json.js +19 -0
  70. package/dist/modules/features/cms/sanity/mergers/package-json.js.map +1 -0
  71. package/dist/modules/features/cms/sanity-pagebuilder/config.d.ts.map +1 -1
  72. package/dist/modules/features/cms/sanity-pagebuilder/config.js +2 -1
  73. package/dist/modules/features/cms/sanity-pagebuilder/config.js.map +1 -1
  74. package/dist/modules/features/cms/sanity-pagebuilder/index.d.ts.map +1 -1
  75. package/dist/modules/features/cms/sanity-pagebuilder/index.js +7 -1
  76. package/dist/modules/features/cms/sanity-pagebuilder/index.js.map +1 -1
  77. package/dist/modules/features/cms/sanity-pagebuilder-cache/config.d.ts +14 -0
  78. package/dist/modules/features/cms/sanity-pagebuilder-cache/config.d.ts.map +1 -0
  79. package/dist/modules/features/cms/sanity-pagebuilder-cache/config.js +50 -0
  80. package/dist/modules/features/cms/sanity-pagebuilder-cache/config.js.map +1 -0
  81. package/dist/modules/features/cms/sanity-pagebuilder-cache/index.d.ts +3 -0
  82. package/dist/modules/features/cms/sanity-pagebuilder-cache/index.d.ts.map +1 -0
  83. package/dist/modules/features/cms/sanity-pagebuilder-cache/index.js +55 -0
  84. package/dist/modules/features/cms/sanity-pagebuilder-cache/index.js.map +1 -0
  85. package/dist/modules/presets/index.d.ts.map +1 -1
  86. package/dist/modules/presets/index.js +2 -0
  87. package/dist/modules/presets/index.js.map +1 -1
  88. package/dist/modules/presets/next-pagebuilder-cache.d.ts +3 -0
  89. package/dist/modules/presets/next-pagebuilder-cache.d.ts.map +1 -0
  90. package/dist/modules/presets/next-pagebuilder-cache.js +10 -0
  91. package/dist/modules/presets/next-pagebuilder-cache.js.map +1 -0
  92. package/dist/paths.d.ts +6 -0
  93. package/dist/paths.d.ts.map +1 -1
  94. package/dist/paths.js +6 -0
  95. package/dist/paths.js.map +1 -1
  96. package/package.json +6 -3
  97. package/src/modules/features/cms/sanity/files/app/api/blog/[slug]/route.ts +4 -1
  98. package/src/modules/features/cms/sanity/files/app/sitemap.md/route.ts +2 -1
  99. package/src/modules/features/cms/sanity/files/components/ui/sanity-image/index.tsx +19 -5
  100. package/src/modules/features/cms/sanity/files/lib/integrations/check-integration.ts +7 -6
  101. package/src/modules/features/cms/sanity/files/lib/integrations/sanity/README.md +32 -1
  102. package/src/modules/features/cms/sanity/files/lib/integrations/sanity/components/rich-text.tsx +1 -0
  103. package/src/modules/features/cms/sanity/files/lib/integrations/sanity/env.ts +2 -2
  104. package/src/modules/features/cms/sanity/files/proxy.ts +9 -0
  105. package/src/modules/features/cms/sanity-pagebuilder/files/app/api/[[...slug]]/route.ts +12 -3
  106. package/src/modules/features/cms/sanity-pagebuilder/files/app/api/revalidate/route.ts +172 -24
  107. package/src/modules/features/cms/sanity-pagebuilder/files/app/feed.xml/route.ts +14 -7
  108. package/src/modules/features/cms/sanity-pagebuilder/files/app/sitemap.md/route.ts +8 -2
  109. package/src/modules/features/cms/sanity-pagebuilder/files/app/studio/[[...tool]]/page.tsx +16 -1
  110. package/src/modules/features/cms/sanity-pagebuilder/files/components/sanity/rich-text.tsx +1 -0
  111. package/src/modules/features/cms/sanity-pagebuilder/files/components/sanity/visual-editing.tsx +2 -2
  112. package/src/modules/features/cms/sanity-pagebuilder/files/components/ui/sanity-image/index.tsx +15 -5
  113. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/check-integration.ts +6 -5
  114. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/client.ts +8 -0
  115. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/slug-field.tsx +13 -7
  116. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/slug-notices.tsx +125 -0
  117. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/env.ts +2 -2
  118. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/fetchers/layout.ts +22 -52
  119. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/icons.ts +2 -2
  120. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/live/index.tsx +4 -2
  121. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/markdown-proxy.config.ts +1 -1
  122. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/page-builder-config.ts +7 -0
  123. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/page-tree-pane.tsx +70 -57
  124. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/queries.ts +119 -29
  125. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/revalidate-paths.ts +70 -0
  126. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/index.ts +1 -0
  127. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/reusable/rich-text-block.ts +39 -0
  128. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/index.ts +2 -0
  129. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/layout/redirect.ts +35 -4
  130. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/page.ts +12 -0
  131. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/tags.ts +56 -0
  132. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/utils/page-builder-markdown.ts +12 -0
  133. package/src/modules/features/cms/sanity-pagebuilder/files/lib/utils/sitemap.ts +11 -8
  134. package/src/modules/features/cms/sanity-pagebuilder/files/proxy.ts +9 -0
  135. package/src/modules/features/cms/sanity-pagebuilder-cache/files/.knip.json +8 -0
  136. package/src/modules/features/cms/sanity-pagebuilder-cache/files/app/api/[[...slug]]/route.ts +112 -0
  137. package/src/modules/features/cms/sanity-pagebuilder-cache/files/app/api/draft-mode/disable/route.ts +7 -0
  138. package/src/modules/features/cms/sanity-pagebuilder-cache/files/app/api/draft-mode/enable/route.ts +20 -0
  139. package/src/modules/features/cms/sanity-pagebuilder-cache/files/app/api/revalidate/route.ts +292 -0
  140. package/src/modules/features/cms/sanity-pagebuilder-cache/files/app/feed.xml/route.ts +66 -0
  141. package/src/modules/features/cms/sanity-pagebuilder-cache/files/app/sitemap.md/route.ts +132 -0
  142. package/src/modules/features/cms/sanity-pagebuilder-cache/files/app/studio/[[...tool]]/layout.tsx +26 -0
  143. package/src/modules/features/cms/sanity-pagebuilder-cache/files/app/studio/[[...tool]]/page.tsx +23 -0
  144. package/src/modules/features/cms/sanity-pagebuilder-cache/files/components/sanity/draft-mode-toggle.tsx +27 -0
  145. package/src/modules/features/cms/sanity-pagebuilder-cache/files/components/sanity/rich-text.tsx +88 -0
  146. package/src/modules/features/cms/sanity-pagebuilder-cache/files/components/sanity/visual-editing.tsx +11 -0
  147. package/src/modules/features/cms/sanity-pagebuilder-cache/files/components/ui/sanity-image/index.tsx +51 -0
  148. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/check-integration.ts +6 -0
  149. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/client.ts +37 -0
  150. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/block-actions-item.tsx +81 -0
  151. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/disable-draft-mode.tsx +23 -0
  152. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/page-builder-input.tsx +39 -0
  153. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/page-category-input.tsx +50 -0
  154. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/same-as-input.tsx +66 -0
  155. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/slug-field.tsx +24 -0
  156. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/slug-input.tsx +14 -0
  157. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/slug-notices.tsx +125 -0
  158. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/confirm-publish-action.ts +40 -0
  159. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/env.ts +34 -0
  160. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/fetchers/layout.ts +39 -0
  161. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/fetchers/redirects.ts +22 -0
  162. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/icons.ts +81 -0
  163. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/live/index.tsx +140 -0
  164. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/markdown-proxy.config.ts +50 -0
  165. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/page-builder-config.ts +144 -0
  166. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/page-category.ts +28 -0
  167. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/page-tree-pane.tsx +431 -0
  168. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/pinned-pages.ts +54 -0
  169. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/presentation.ts +119 -0
  170. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/queries.ts +548 -0
  171. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/revalidate-paths.ts +70 -0
  172. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/sanity.cli.ts +29 -0
  173. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/sanity.config.ts +116 -0
  174. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/index.ts +6 -0
  175. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/reusable/blog-content.ts +89 -0
  176. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/reusable/description.ts +29 -0
  177. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/reusable/gated-content.ts +178 -0
  178. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/reusable/hero.ts +28 -0
  179. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/reusable/rich-text-block.ts +39 -0
  180. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/singleton/content-collection.ts +45 -0
  181. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/content/author.ts +70 -0
  182. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/content/blog-category.ts +55 -0
  183. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/index.ts +134 -0
  184. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/layout/company-data.ts +70 -0
  185. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/layout/footer.ts +79 -0
  186. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/layout/navbar.ts +75 -0
  187. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/layout/redirect.ts +152 -0
  188. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/layout/structured-data.ts +124 -0
  189. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/link.ts +125 -0
  190. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/logo-field.ts +9 -0
  191. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/metadata.ts +132 -0
  192. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/nav-objects.ts +192 -0
  193. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/page-builder.ts +43 -0
  194. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/page-folder.ts +124 -0
  195. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/page.ts +265 -0
  196. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/richText.ts +63 -0
  197. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/structured-data-blocks.ts +436 -0
  198. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/singletons.ts +51 -0
  199. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/structure.ts +334 -0
  200. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/tags.ts +89 -0
  201. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/utils/image.ts +8 -0
  202. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/utils/link.ts +140 -0
  203. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/utils/page-builder-markdown.ts +113 -0
  204. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/utils/page-json-ld.ts +208 -0
  205. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/scripts/sanity-typegen.ts +36 -0
  206. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/utils/check-redirect.ts +30 -0
  207. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/utils/json-ld.tsx +522 -0
  208. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/utils/metadata.ts +189 -0
  209. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/utils/rss.ts +96 -0
  210. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/utils/sitemap.ts +58 -0
  211. package/src/modules/features/cms/sanity-pagebuilder-cache/files/proxy.ts +90 -0
  212. package/src/modules/features/env/dotenvx/files/vercelignore +5 -0
  213. package/src/templates/next-default/.env.example +4 -2
  214. package/src/templates/next-default/.github/workflows/ci.yml +1 -1
  215. package/src/templates/next-default/.vscode/settings.json +0 -2
  216. package/src/templates/next-default/app/robots.ts +2 -3
  217. package/src/templates/next-default/components/ui/image/index.tsx +2 -4
  218. package/src/templates/next-default/knip.json +1 -1
  219. package/src/templates/next-default/lefthook.yml +1 -1
  220. package/src/templates/next-default/lib/utils/README.md +18 -5
  221. package/src/templates/next-default/lib/utils/image-sizes.ts +72 -0
  222. package/src/templates/next-default/lib/utils/merge-refs.ts +56 -0
  223. package/src/templates/next-default/lib/utils/strings.ts +1 -51
  224. package/src/templates/next-default/next.config.ts +38 -5
  225. package/src/templates/next-default/package.json +9 -12
  226. package/src/templates/next-experiments/.env.example +4 -2
  227. package/src/templates/next-experiments/.github/workflows/ci.yml +1 -1
  228. package/src/templates/next-experiments/.vscode/settings.json +0 -2
  229. package/src/templates/next-experiments/app/page.tsx +1 -1
  230. package/src/templates/next-experiments/app/robots.ts +2 -3
  231. package/src/templates/next-experiments/components/ui/image/index.tsx +2 -4
  232. package/src/templates/next-experiments/knip.json +1 -1
  233. package/src/templates/next-experiments/lefthook.yml +1 -1
  234. package/src/templates/next-experiments/lib/utils/README.md +18 -5
  235. package/src/templates/next-experiments/lib/utils/image-sizes.ts +72 -0
  236. package/src/templates/next-experiments/lib/utils/merge-refs.ts +56 -0
  237. package/src/templates/next-experiments/lib/utils/strings.ts +1 -51
  238. package/src/templates/next-experiments/next.config.ts +38 -5
  239. package/src/templates/next-experiments/package.json +9 -12
  240. package/src/templates/next-pagebuilder/.env.example +3 -0
  241. package/src/templates/next-pagebuilder/.github/workflows/ci.yml +1 -1
  242. package/src/templates/next-pagebuilder/.vscode/settings.json +0 -2
  243. package/src/templates/next-pagebuilder/README.md +95 -0
  244. package/src/templates/next-pagebuilder/app/(content)/[[...slug]]/page.tsx +24 -9
  245. package/src/templates/next-pagebuilder/app/robots.ts +2 -3
  246. package/src/templates/next-pagebuilder/app/sitemap.xml/route.ts +9 -4
  247. package/src/templates/next-pagebuilder/biome.json +2 -1
  248. package/src/templates/next-pagebuilder/components/layout/footer/index.tsx +3 -6
  249. package/src/templates/next-pagebuilder/components/layout/header/index.tsx +3 -2
  250. package/src/templates/next-pagebuilder/components/layout/json-ld/index.tsx +3 -8
  251. package/src/templates/next-pagebuilder/components/page-builder/components/content-collection/content-filters.tsx +23 -40
  252. package/src/templates/next-pagebuilder/components/page-builder/components/content-collection/index.tsx +88 -58
  253. package/src/templates/next-pagebuilder/components/page-builder/components/content-collection/skeleton.tsx +41 -0
  254. package/src/templates/next-pagebuilder/components/page-builder/components/content-collection/types.ts +14 -1
  255. package/src/templates/next-pagebuilder/components/page-builder/components/rich-text-block.tsx +16 -0
  256. package/src/templates/next-pagebuilder/components/page-builder/renderer.tsx +23 -3
  257. package/src/templates/next-pagebuilder/components/page-document/index.tsx +1 -0
  258. package/src/templates/next-pagebuilder/components/ui/image/index.tsx +2 -1
  259. package/src/templates/next-pagebuilder/knip.json +3 -2
  260. package/src/templates/next-pagebuilder/lefthook.yml +1 -1
  261. package/src/templates/next-pagebuilder/lib/integrations/sanity/sanity.types.ts +1221 -977
  262. package/src/templates/next-pagebuilder/lib/utils/image-sizes.ts +72 -0
  263. package/src/templates/next-pagebuilder/lib/utils/merge-refs.ts +56 -0
  264. package/src/templates/next-pagebuilder/next.config.ts +35 -5
  265. package/src/templates/next-pagebuilder/package.json +8 -10
  266. package/src/templates/next-pagebuilder-cache/.biome/plugins/README.md +21 -0
  267. package/src/templates/next-pagebuilder-cache/.biome/plugins/no-anchor-element.grit +12 -0
  268. package/src/templates/next-pagebuilder-cache/.biome/plugins/no-relative-parent-imports.grit +10 -0
  269. package/src/templates/next-pagebuilder-cache/.biome/plugins/no-unnecessary-forwardref.grit +9 -0
  270. package/src/templates/next-pagebuilder-cache/.env.example +40 -0
  271. package/src/templates/next-pagebuilder-cache/.github/PULL_REQUEST_TEMPLATE.md +13 -0
  272. package/src/templates/next-pagebuilder-cache/.github/workflows/ci.yml +105 -0
  273. package/src/templates/next-pagebuilder-cache/.vscode/extensions.json +19 -0
  274. package/src/templates/next-pagebuilder-cache/.vscode/settings.json +103 -0
  275. package/src/templates/next-pagebuilder-cache/.zed/settings.json +47 -0
  276. package/src/templates/next-pagebuilder-cache/README.md +212 -0
  277. package/src/templates/next-pagebuilder-cache/_gitignore +65 -0
  278. package/src/templates/next-pagebuilder-cache/app/(content)/[[...slug]]/page.tsx +124 -0
  279. package/src/templates/next-pagebuilder-cache/app/(content)/layout.tsx +28 -0
  280. package/src/templates/next-pagebuilder-cache/app/actions/unlock-gated-content.ts +53 -0
  281. package/src/templates/next-pagebuilder-cache/app/favicon.ico +0 -0
  282. package/src/templates/next-pagebuilder-cache/app/layout.tsx +83 -0
  283. package/src/templates/next-pagebuilder-cache/app/robots.ts +13 -0
  284. package/src/templates/next-pagebuilder-cache/app/sitemap.xml/route.ts +84 -0
  285. package/src/templates/next-pagebuilder-cache/biome.json +257 -0
  286. package/src/templates/next-pagebuilder-cache/components/layout/footer/index.tsx +127 -0
  287. package/src/templates/next-pagebuilder-cache/components/layout/header/components/cta-button.tsx +28 -0
  288. package/src/templates/next-pagebuilder-cache/components/layout/header/components/mega-menu-panel.tsx +90 -0
  289. package/src/templates/next-pagebuilder-cache/components/layout/header/components/nav-item-renderer.tsx +98 -0
  290. package/src/templates/next-pagebuilder-cache/components/layout/header/components/nav-leaf-item.tsx +33 -0
  291. package/src/templates/next-pagebuilder-cache/components/layout/header/components/types.ts +7 -0
  292. package/src/templates/next-pagebuilder-cache/components/layout/header/header-client.tsx +110 -0
  293. package/src/templates/next-pagebuilder-cache/components/layout/header/index.tsx +32 -0
  294. package/src/templates/next-pagebuilder-cache/components/layout/json-ld/index.tsx +79 -0
  295. package/src/templates/next-pagebuilder-cache/components/layout/wrapper/index.tsx +33 -0
  296. package/src/templates/next-pagebuilder-cache/components/page-builder/components/article-content/index.tsx +78 -0
  297. package/src/templates/next-pagebuilder-cache/components/page-builder/components/article-content/related-post-item.tsx +27 -0
  298. package/src/templates/next-pagebuilder-cache/components/page-builder/components/content-collection/content-card.tsx +66 -0
  299. package/src/templates/next-pagebuilder-cache/components/page-builder/components/content-collection/content-filters.tsx +76 -0
  300. package/src/templates/next-pagebuilder-cache/components/page-builder/components/content-collection/content-grid.tsx +40 -0
  301. package/src/templates/next-pagebuilder-cache/components/page-builder/components/content-collection/content-pagination-nav.tsx +71 -0
  302. package/src/templates/next-pagebuilder-cache/components/page-builder/components/content-collection/index.tsx +306 -0
  303. package/src/templates/next-pagebuilder-cache/components/page-builder/components/content-collection/skeleton.tsx +41 -0
  304. package/src/templates/next-pagebuilder-cache/components/page-builder/components/content-collection/types.ts +32 -0
  305. package/src/templates/next-pagebuilder-cache/components/page-builder/components/description.tsx +17 -0
  306. package/src/templates/next-pagebuilder-cache/components/page-builder/components/gated-content/index.tsx +169 -0
  307. package/src/templates/next-pagebuilder-cache/components/page-builder/components/hero.tsx +17 -0
  308. package/src/templates/next-pagebuilder-cache/components/page-builder/components/rich-text-block.tsx +16 -0
  309. package/src/templates/next-pagebuilder-cache/components/page-builder/renderer.tsx +71 -0
  310. package/src/templates/next-pagebuilder-cache/components/page-builder/types.ts +25 -0
  311. package/src/templates/next-pagebuilder-cache/components/page-document/index.tsx +147 -0
  312. package/src/templates/next-pagebuilder-cache/components/ui/image/index.tsx +216 -0
  313. package/src/templates/next-pagebuilder-cache/components/ui/link/index.tsx +152 -0
  314. package/src/templates/next-pagebuilder-cache/components.json +24 -0
  315. package/src/templates/next-pagebuilder-cache/knip.json +35 -0
  316. package/src/templates/next-pagebuilder-cache/lefthook.yml +25 -0
  317. package/src/templates/next-pagebuilder-cache/lib/integrations/sanity/sanity.types.ts +1588 -0
  318. package/src/templates/next-pagebuilder-cache/lib/scripts/setup-ci-secrets.ts +117 -0
  319. package/src/templates/next-pagebuilder-cache/lib/styles/cn.ts +5 -0
  320. package/src/templates/next-pagebuilder-cache/lib/styles/global.css +69 -0
  321. package/src/templates/next-pagebuilder-cache/lib/utils/base-url.ts +17 -0
  322. package/src/templates/next-pagebuilder-cache/lib/utils/format-date.ts +8 -0
  323. package/src/templates/next-pagebuilder-cache/lib/utils/slug-tag.ts +6 -0
  324. package/src/templates/next-pagebuilder-cache/next.config.ts +176 -0
  325. package/src/templates/next-pagebuilder-cache/package.json +62 -0
  326. package/src/templates/next-pagebuilder-cache/postcss.config.mjs +65 -0
  327. package/src/templates/next-pagebuilder-cache/svg.d.ts +6 -0
  328. package/src/templates/next-pagebuilder-cache/tsconfig.json +38 -0
  329. package/src/templates/next-webgl/.env.example +4 -2
  330. package/src/templates/next-webgl/.github/workflows/ci.yml +1 -1
  331. package/src/templates/next-webgl/.vscode/settings.json +0 -2
  332. package/src/templates/next-webgl/app/robots.ts +2 -3
  333. package/src/templates/next-webgl/components/ui/image/index.tsx +2 -4
  334. package/src/templates/next-webgl/knip.json +1 -1
  335. package/src/templates/next-webgl/lefthook.yml +1 -1
  336. package/src/templates/next-webgl/lib/utils/README.md +18 -5
  337. package/src/templates/next-webgl/lib/utils/image-sizes.ts +72 -0
  338. package/src/templates/next-webgl/lib/utils/merge-refs.ts +56 -0
  339. package/src/templates/next-webgl/lib/utils/strings.ts +1 -51
  340. package/src/templates/next-webgl/next.config.ts +38 -5
  341. package/src/templates/next-webgl/package.json +11 -14
  342. package/src/templates/next-experiments/tsconfig.tsbuildinfo +0 -1
  343. package/src/templates/next-pagebuilder/app/actions/refresh.ts +0 -5
  344. package/src/templates/next-webgl/tsconfig.tsbuildinfo +0 -1
@@ -0,0 +1,144 @@
1
+ import { contentIcon, pageIcon, type SanityStudioIcon } from "./icons";
2
+ import { singletonComponents } from "./singletons";
3
+
4
+ export type PageBuilderDocumentType =
5
+ | "hero"
6
+ | "description"
7
+ | "richTextBlock"
8
+ | "gatedContent"
9
+ | "blogContent"
10
+ | (typeof singletonComponents)[number]["schemaType"];
11
+
12
+ type PageBuilderReferenceMember = {
13
+ documentType: PageBuilderDocumentType;
14
+ name: string;
15
+ title: string;
16
+ documentId?: string;
17
+ groups: PageBuilderGroupName[];
18
+ };
19
+
20
+ export type PageBuilderGroupName = "generic" | "blogEntry";
21
+
22
+ export const pageBuilderPageTypes: Array<{
23
+ title: string;
24
+ value: PageBuilderGroupName;
25
+ }> = [
26
+ { title: "Generic", value: "generic" },
27
+ { title: "Blog Entry", value: "blogEntry" },
28
+ ];
29
+
30
+ const pageCategoryIcons: Record<PageBuilderGroupName, SanityStudioIcon> = {
31
+ generic: pageIcon,
32
+ blogEntry: contentIcon,
33
+ };
34
+
35
+ export const getPageCategoryIcon = (
36
+ pageType: PageBuilderGroupName | undefined | null,
37
+ ) => pageCategoryIcons[pageType ?? "generic"];
38
+
39
+ const reusablePageBuilderReferenceMembers: PageBuilderReferenceMember[] = [
40
+ {
41
+ documentType: "hero",
42
+ name: "heroReference",
43
+ title: "Heading",
44
+ groups: ["generic"],
45
+ },
46
+ {
47
+ documentType: "description",
48
+ name: "descriptionReference",
49
+ title: "Description",
50
+ groups: ["generic"],
51
+ },
52
+ {
53
+ documentType: "richTextBlock",
54
+ name: "richTextBlockReference",
55
+ title: "Rich Text",
56
+ groups: ["generic"],
57
+ },
58
+ {
59
+ documentType: "gatedContent",
60
+ name: "gatedContentReference",
61
+ title: "Gated Content",
62
+ groups: ["generic"],
63
+ },
64
+ {
65
+ documentType: "blogContent",
66
+ name: "blogContentReference",
67
+ title: "Blog Content",
68
+ groups: ["blogEntry"],
69
+ },
70
+ ];
71
+
72
+ const singletonPageBuilderReferenceMembers: PageBuilderReferenceMember[] =
73
+ singletonComponents.map((component) => ({
74
+ documentType: component.schemaType,
75
+ name: `${component.schemaType}Reference`,
76
+ title: component.title,
77
+ documentId: component.documentId,
78
+ groups: ["generic"],
79
+ }));
80
+
81
+ export const pageBuilderReferenceMembers: PageBuilderReferenceMember[] = [
82
+ ...reusablePageBuilderReferenceMembers,
83
+ ...singletonPageBuilderReferenceMembers,
84
+ ];
85
+
86
+ export const getPageBuilderReferenceMember = (name: string) =>
87
+ pageBuilderReferenceMembers.find((member) => member.name === name);
88
+
89
+ export const getPageBuilderReferenceMemberByDocumentType = (
90
+ documentType: PageBuilderDocumentType,
91
+ ) =>
92
+ pageBuilderReferenceMembers.find(
93
+ (member) => member.documentType === documentType,
94
+ );
95
+
96
+ const isPageBuilderGroupVisible = (
97
+ pageType: PageBuilderGroupName | undefined,
98
+ group: PageBuilderGroupName,
99
+ ) => (pageType ?? "generic") === group;
100
+
101
+ export const getVisiblePageBuilderReferenceMembers = (
102
+ pageType: PageBuilderGroupName | undefined,
103
+ ) =>
104
+ pageBuilderReferenceMembers.filter((member) =>
105
+ member.groups.some((group) => isPageBuilderGroupVisible(pageType, group)),
106
+ );
107
+
108
+ export const getVisiblePageBuilderInsertMenuGroups = (
109
+ pageType: PageBuilderGroupName | undefined,
110
+ ) =>
111
+ pageBuilderInsertMenuGroups.filter(
112
+ (group) => group.name === (pageType ?? "generic"),
113
+ );
114
+
115
+ export const getInvalidPageBuilderDocumentTypes = (
116
+ pageType: PageBuilderGroupName | undefined,
117
+ documentTypes: PageBuilderDocumentType[],
118
+ ) =>
119
+ documentTypes.filter((documentType) => {
120
+ const member = getPageBuilderReferenceMemberByDocumentType(documentType);
121
+
122
+ if (!member) return false;
123
+
124
+ return !member.groups.some((group) =>
125
+ isPageBuilderGroupVisible(pageType, group),
126
+ );
127
+ });
128
+
129
+ export const pageBuilderInsertMenuGroups = [
130
+ {
131
+ name: "generic",
132
+ title: "Generic",
133
+ of: pageBuilderReferenceMembers
134
+ .filter((member) => member.groups.includes("generic"))
135
+ .map((member) => member.name),
136
+ },
137
+ {
138
+ name: "blogEntry",
139
+ title: "Blog Entry",
140
+ of: pageBuilderReferenceMembers
141
+ .filter((member) => member.groups.includes("blogEntry"))
142
+ .map((member) => member.name),
143
+ },
144
+ ];
@@ -0,0 +1,28 @@
1
+ import type { PageBuilderGroupName } from "@/lib/integrations/sanity/page-builder-config";
2
+
3
+ type PageCategoryParams = {
4
+ slug?: string | null;
5
+ folder?: string | null;
6
+ };
7
+
8
+ export const isEmptyPageSlug = (slug?: string | null) =>
9
+ slug == null || slug === "" || slug === "/";
10
+
11
+ export const getGeneratedPageCategory = ({
12
+ slug,
13
+ folder,
14
+ }: PageCategoryParams): PageBuilderGroupName => {
15
+ if (!isEmptyPageSlug(slug) && folder === "blog") return "blogEntry";
16
+
17
+ return "generic";
18
+ };
19
+
20
+ export const pageCategoryExpression = `
21
+ select(
22
+ defined(slug.current) &&
23
+ slug.current != "" &&
24
+ slug.current != "/" &&
25
+ pageFolder->slug.current == "blog" => "blogEntry",
26
+ "generic"
27
+ )
28
+ `;
@@ -0,0 +1,431 @@
1
+ import {
2
+ MagnifyingGlassIcon,
3
+ PushPinIcon,
4
+ } from "@phosphor-icons/react/dist/ssr";
5
+ import {
6
+ Badge,
7
+ Box,
8
+ Card,
9
+ Flex,
10
+ Spinner,
11
+ Stack,
12
+ Text,
13
+ TextInput,
14
+ } from "@sanity/ui";
15
+ import { type ReactNode, useEffect, useRef, useState } from "react";
16
+ import { asyncScheduler, from, merge, of } from "rxjs";
17
+ import { switchMap, throttleTime } from "rxjs/operators";
18
+ import { type SanityClient, useClient } from "sanity";
19
+ import { usePaneRouter } from "sanity/structure";
20
+ import { apiVersion } from "./env";
21
+ import { pageFolderIcon } from "./icons";
22
+ import {
23
+ getPageCategoryIcon,
24
+ type PageBuilderGroupName,
25
+ } from "./page-builder-config";
26
+ import { pageCategoryExpression } from "./page-category";
27
+ import {
28
+ PINNED_PAGE_ENTRIES,
29
+ PINNED_PAGE_IDS,
30
+ type PinnedPageEntry,
31
+ } from "./pinned-pages";
32
+
33
+ const NO_MATCHES = "No matches found";
34
+ const NO_PAGES_OR_FOLDERS = "No pages or folders here yet";
35
+
36
+ interface PageTreeItem {
37
+ _id: string;
38
+ _originalId?: string;
39
+ _type: "page" | "pageFolder";
40
+ title?: string | null;
41
+ slug?: string | null;
42
+ pageType?: PageBuilderGroupName | null;
43
+ }
44
+
45
+ // `_originalId` is prefixed `drafts.` when an unpublished edit exists.
46
+ const isDraftItem = (item: PageTreeItem) =>
47
+ !!item._originalId?.startsWith("drafts.");
48
+
49
+ type ActiveState = "leaf" | "ancestor" | "none";
50
+
51
+ const PAGE_TREE_ITEM_ID_PREFIX = {
52
+ page: "page-tree-",
53
+ pageFolder: "page-folder-tree-",
54
+ } as const;
55
+
56
+ const PAGE_TREE_PANE_STYLE_ID = "page-tree-pane-style";
57
+
58
+ // Constrain the tree pane width so the slug/title rows stay readable. Runs once
59
+ // (id-guarded) from inside the component so the injection lives in React's tree.
60
+ const usePaneWidthStyle = () => {
61
+ useEffect(() => {
62
+ if (
63
+ typeof document === "undefined" ||
64
+ document.getElementById(PAGE_TREE_PANE_STYLE_ID)
65
+ ) {
66
+ return;
67
+ }
68
+ const style = document.createElement("style");
69
+ style.id = PAGE_TREE_PANE_STYLE_ID;
70
+ style.textContent = `[data-ui="Pane"][id^="page-tree-"]{max-width:350px;}`;
71
+ document.head.appendChild(style);
72
+ }, []);
73
+ };
74
+
75
+ const PAGE_TREE_QUERY = `
76
+ *[
77
+ (
78
+ _type == "pageFolder" &&
79
+ (
80
+ ($folderId == null && !defined(parentFolder._ref)) ||
81
+ parentFolder._ref == $folderId
82
+ )
83
+ ) ||
84
+ (
85
+ _type == "page" &&
86
+ (
87
+ ($folderId == null && !defined(pageFolder._ref)) ||
88
+ pageFolder._ref == $folderId
89
+ )
90
+ )
91
+ ]{
92
+ _id,
93
+ _originalId,
94
+ _type,
95
+ title,
96
+ "slug": slug.current,
97
+ "pageType": ${pageCategoryExpression}
98
+ }
99
+ `;
100
+
101
+ const normalizeId = (id: string) => id.replace(/^drafts\./, "");
102
+
103
+ const getItemId = (item: Pick<PageTreeItem, "_id" | "_type">) =>
104
+ `${PAGE_TREE_ITEM_ID_PREFIX[item._type]}${normalizeId(item._id)}`;
105
+
106
+ const sortItems = (items: PageTreeItem[]) =>
107
+ [...items].sort((a, b) => {
108
+ if (a._type !== b._type) return a._type === "page" ? -1 : 1;
109
+ // Pages without a slug (e.g. the homepage) float to the top.
110
+ if (!a.slug && a._type === "page") return -1;
111
+ if (!b.slug && b._type === "page") return 1;
112
+ return (a.title ?? "").localeCompare(b.title ?? "");
113
+ });
114
+
115
+ interface PageTreePaneProps {
116
+ options?: Record<string, unknown>;
117
+ }
118
+
119
+ export const PageTreePane = ({ options }: PageTreePaneProps) => {
120
+ const folderId = (options?.folderId as string | null | undefined) ?? null;
121
+
122
+ usePaneWidthStyle();
123
+
124
+ const [items, setItems] = useState<PageTreeItem[]>([]);
125
+ const [loading, setLoading] = useState(true);
126
+ const [query, setQuery] = useState("");
127
+
128
+ const baseClient = useClient({ apiVersion });
129
+ // Pinned to drafts so the tree shows in-progress edits and the Draft badge.
130
+ const clientRef = useRef<SanityClient | null>(null);
131
+ if (!clientRef.current) {
132
+ clientRef.current = baseClient.withConfig({ perspective: "drafts" });
133
+ }
134
+ const client = clientRef.current;
135
+
136
+ const { ChildLink, routerPanesState, groupIndex } = usePaneRouter();
137
+ const activeChildId = routerPanesState[groupIndex + 1]?.[0]?.id ?? null;
138
+ const isLeafPane = routerPanesState.length === groupIndex + 2;
139
+
140
+ useEffect(() => {
141
+ setLoading(true);
142
+ // Initial fetch, then refetch on each folder-scoped mutation to stay live.
143
+ const subscription = merge(
144
+ of(null),
145
+ client
146
+ .listen<PageTreeItem>(
147
+ folderId == null
148
+ ? `*[_type in ["page", "pageFolder"] && !defined(parentFolder._ref) && !defined(pageFolder._ref)]`
149
+ : `*[_type in ["page", "pageFolder"] && (parentFolder._ref == $folderId || pageFolder._ref == $folderId)]`,
150
+ { folderId },
151
+ { visibility: "query", events: ["mutation"], includeResult: false },
152
+ )
153
+ .pipe(
154
+ // leading+trailing: react to the first edit at once, coalesce the rest.
155
+ throttleTime(300, asyncScheduler, {
156
+ leading: true,
157
+ trailing: true,
158
+ }),
159
+ ),
160
+ )
161
+ .pipe(
162
+ switchMap(() =>
163
+ from(client.fetch<PageTreeItem[]>(PAGE_TREE_QUERY, { folderId })),
164
+ ),
165
+ )
166
+ .subscribe({
167
+ next: (data) => {
168
+ setItems(data);
169
+ setLoading(false);
170
+ },
171
+ error: () => setLoading(false),
172
+ });
173
+
174
+ return () => subscription.unsubscribe();
175
+ }, [folderId, client]);
176
+
177
+ // Pinned pages get their own section, so drop them from the dynamic list.
178
+ const visible =
179
+ folderId == null
180
+ ? items.filter((item) => !PINNED_PAGE_IDS.has(item._id))
181
+ : items;
182
+ const sorted = sortItems(visible);
183
+ const term = query.toLowerCase().trim();
184
+ const filtered = term
185
+ ? sorted.filter(
186
+ (item) =>
187
+ item.title?.toLowerCase().includes(term) ||
188
+ item.slug?.toLowerCase().includes(term),
189
+ )
190
+ : sorted;
191
+ const showPinned = folderId == null && !term;
192
+
193
+ return (
194
+ <Flex direction="column" style={{ height: "100%" }}>
195
+ <PageTreeSearch
196
+ query={query}
197
+ onQueryChange={setQuery}
198
+ autoFocus={folderId == null}
199
+ />
200
+ <Box style={{ borderBottom: "1px solid var(--card-border-color)" }} />
201
+ {showPinned && (
202
+ <Stack>
203
+ {PINNED_PAGE_ENTRIES.map((entry) => {
204
+ let activeState: ActiveState = "none";
205
+ if (activeChildId === entry.id) {
206
+ activeState = isLeafPane ? "leaf" : "ancestor";
207
+ }
208
+ return (
209
+ <PinnedRow
210
+ key={entry.id}
211
+ entry={entry}
212
+ ChildLink={ChildLink}
213
+ activeState={activeState}
214
+ />
215
+ );
216
+ })}
217
+ </Stack>
218
+ )}
219
+ <TreeBody
220
+ loading={loading}
221
+ items={filtered}
222
+ emptyMessage={term ? NO_MATCHES : NO_PAGES_OR_FOLDERS}
223
+ ChildLink={ChildLink}
224
+ activeChildId={activeChildId}
225
+ isLeafPane={isLeafPane}
226
+ />
227
+ </Flex>
228
+ );
229
+ };
230
+
231
+ interface PageTreeSearchProps {
232
+ query: string;
233
+ onQueryChange: (value: string) => void;
234
+ autoFocus?: boolean;
235
+ }
236
+
237
+ const PageTreeSearch = ({
238
+ query,
239
+ onQueryChange,
240
+ autoFocus,
241
+ }: PageTreeSearchProps) => (
242
+ <Box padding={3}>
243
+ <TextInput
244
+ icon={MagnifyingGlassIcon}
245
+ placeholder="Filter pages and folders…"
246
+ value={query}
247
+ onChange={(e) => onQueryChange(e.currentTarget.value)}
248
+ clearButton={!!query}
249
+ onClear={() => onQueryChange("")}
250
+ autoFocus={autoFocus}
251
+ />
252
+ </Box>
253
+ );
254
+
255
+ interface TreeBodyProps {
256
+ loading: boolean;
257
+ items: PageTreeItem[];
258
+ emptyMessage: string;
259
+ ChildLink: ReturnType<typeof usePaneRouter>["ChildLink"];
260
+ activeChildId: string | null;
261
+ isLeafPane: boolean;
262
+ }
263
+
264
+ const TreeBody = ({
265
+ loading,
266
+ items,
267
+ emptyMessage,
268
+ ChildLink,
269
+ activeChildId,
270
+ isLeafPane,
271
+ }: TreeBodyProps) => (
272
+ <Box style={{ flex: 1, overflowY: "auto" }}>
273
+ {loading && <Loading />}
274
+ {!loading && items.length === 0 && <Empty message={emptyMessage} />}
275
+ {!loading && items.length > 0 && (
276
+ <Stack>
277
+ {items.map((item) => {
278
+ const isActive = getItemId(item) === activeChildId;
279
+ let activeState: ActiveState = "none";
280
+ if (isActive) activeState = isLeafPane ? "leaf" : "ancestor";
281
+ return (
282
+ <ItemRow
283
+ key={item._id}
284
+ item={item}
285
+ ChildLink={ChildLink}
286
+ activeState={activeState}
287
+ />
288
+ );
289
+ })}
290
+ </Stack>
291
+ )}
292
+ </Box>
293
+ );
294
+
295
+ const Loading = () => (
296
+ <Flex justify="center" padding={4}>
297
+ <Spinner />
298
+ </Flex>
299
+ );
300
+
301
+ const Empty = ({ message }: { message: string }) => (
302
+ <Flex padding={4} justify="center">
303
+ <Text muted size={1}>
304
+ {message}
305
+ </Text>
306
+ </Flex>
307
+ );
308
+
309
+ interface ItemRowProps {
310
+ item: PageTreeItem;
311
+ ChildLink: ReturnType<typeof usePaneRouter>["ChildLink"];
312
+ activeState: ActiveState;
313
+ }
314
+
315
+ const ItemIcon = ({ item }: { item: PageTreeItem }) => {
316
+ const Icon =
317
+ item._type === "pageFolder"
318
+ ? pageFolderIcon
319
+ : getPageCategoryIcon(item.pageType);
320
+ return <Icon />;
321
+ };
322
+
323
+ // One presentation for every row in the tree, so pinned entries and documents
324
+ // can't drift apart on spacing or which lines they show.
325
+ const TreeRow = ({
326
+ childId,
327
+ icon,
328
+ title,
329
+ path,
330
+ badge,
331
+ ChildLink,
332
+ activeState,
333
+ }: {
334
+ childId: string;
335
+ icon: ReactNode;
336
+ title: string;
337
+ path: string | null;
338
+ badge?: ReactNode;
339
+ ChildLink: ReturnType<typeof usePaneRouter>["ChildLink"];
340
+ activeState: ActiveState;
341
+ }) => {
342
+ const isLeaf = activeState === "leaf";
343
+ const isAncestor = activeState === "ancestor";
344
+
345
+ return (
346
+ <ChildLink childId={childId}>
347
+ <Card
348
+ as="div"
349
+ padding={3}
350
+ radius={0}
351
+ pressed={isLeaf}
352
+ selected={isAncestor}
353
+ tone={isLeaf ? "primary" : "default"}
354
+ style={{
355
+ cursor: "pointer",
356
+ display: "block",
357
+ ...(isLeaf && {
358
+ backgroundColor: "var(--card-badge-primary-bg-color)",
359
+ color: "var(--card-badge-primary-fg-color)",
360
+ }),
361
+ ...(isAncestor && {
362
+ backgroundColor: "var(--card-muted-bg-color)",
363
+ }),
364
+ }}
365
+ >
366
+ <Flex align="center" gap={3}>
367
+ {/* Sized here rather than by the caller: Phosphor icons are 1em, so
368
+ one left to inherit the ambient font size renders wider than one
369
+ wrapped in <Text> and pushes its whole row out of line. */}
370
+ <Box style={{ flexShrink: 0 }}>
371
+ <Text size={1}>{icon}</Text>
372
+ </Box>
373
+ <Stack space={2} flex={1}>
374
+ <Flex align="center" gap={2}>
375
+ <Text size={1} weight="medium">
376
+ {title}
377
+ </Text>
378
+ {badge}
379
+ </Flex>
380
+ {path && (
381
+ <Text size={0} muted weight="regular">
382
+ {path}
383
+ </Text>
384
+ )}
385
+ </Stack>
386
+ </Flex>
387
+ </Card>
388
+ </ChildLink>
389
+ );
390
+ };
391
+
392
+ const ItemRow = ({ item, ChildLink, activeState }: ItemRowProps) => {
393
+ const fallback = item._type === "page" ? "Untitled Page" : "Untitled Folder";
394
+
395
+ return (
396
+ <TreeRow
397
+ childId={getItemId(item)}
398
+ icon={<ItemIcon item={item} />}
399
+ title={item.title ?? fallback}
400
+ path={item.slug ? `/${item.slug}` : null}
401
+ badge={
402
+ isDraftItem(item) ? (
403
+ <Badge fontSize={0} mode="outline" tone="caution">
404
+ Draft
405
+ </Badge>
406
+ ) : undefined
407
+ }
408
+ ChildLink={ChildLink}
409
+ activeState={activeState}
410
+ />
411
+ );
412
+ };
413
+
414
+ const PinnedRow = ({
415
+ entry,
416
+ ChildLink,
417
+ activeState,
418
+ }: {
419
+ entry: PinnedPageEntry;
420
+ ChildLink: ReturnType<typeof usePaneRouter>["ChildLink"];
421
+ activeState: ActiveState;
422
+ }) => (
423
+ <TreeRow
424
+ childId={entry.id}
425
+ icon={<PushPinIcon />}
426
+ title={entry.title}
427
+ path={entry.path}
428
+ ChildLink={ChildLink}
429
+ activeState={activeState}
430
+ />
431
+ );
@@ -0,0 +1,54 @@
1
+ import { pageIcon, type SanityStudioIcon } from "./icons";
2
+
3
+ export type PinnedPageEntry = {
4
+ /** Sanity document _id. Doubles as the Studio template id. */
5
+ id: string;
6
+ /** Sanity document draft _id (`drafts.${id}`). */
7
+ draftId: string;
8
+ /** Title shown in Studio listings and used as the initial `title` value. */
9
+ title: string;
10
+ /** Icon shown in Studio. */
11
+ icon: SanityStudioIcon;
12
+ /** Public URL path. Used for link resolution + presentation. */
13
+ path: string;
14
+ /** Slug.current seeded into the template. `null` means no slug (homepage). */
15
+ slug: string | null;
16
+ };
17
+
18
+ export const HOMEPAGE_PINNED_PAGE: PinnedPageEntry = {
19
+ id: "page-homepage",
20
+ draftId: "drafts.page-homepage",
21
+ title: "Homepage",
22
+ icon: pageIcon,
23
+ path: "/",
24
+ slug: null,
25
+ };
26
+
27
+ export const BLOG_INDEX_PINNED_PAGE: PinnedPageEntry = {
28
+ id: "page-blog-index",
29
+ draftId: "drafts.page-blog-index",
30
+ title: "Blog Index",
31
+ icon: pageIcon,
32
+ path: "/blog",
33
+ slug: "blog",
34
+ };
35
+
36
+ export const PINNED_PAGE_ENTRIES: PinnedPageEntry[] = [
37
+ HOMEPAGE_PINNED_PAGE,
38
+ BLOG_INDEX_PINNED_PAGE,
39
+ ];
40
+
41
+ export const PINNED_PAGE_IDS: ReadonlySet<string> = new Set(
42
+ PINNED_PAGE_ENTRIES.flatMap((entry) => [entry.id, entry.draftId]),
43
+ );
44
+
45
+ const pinnedPageById = new Map(
46
+ PINNED_PAGE_ENTRIES.flatMap((entry) => [
47
+ [entry.id, entry] as const,
48
+ [entry.draftId, entry] as const,
49
+ ]),
50
+ );
51
+
52
+ /** Look up a pinned page entry by either its published or draft id. */
53
+ export const getPinnedPageById = (id: string | null | undefined) =>
54
+ id ? (pinnedPageById.get(id) ?? null) : null;