bsmnt 0.2.10 → 0.3.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 (163) hide show
  1. package/dist/configs/skills.d.ts +27 -0
  2. package/dist/configs/skills.d.ts.map +1 -0
  3. package/dist/configs/skills.js +18 -0
  4. package/dist/configs/skills.js.map +1 -0
  5. package/dist/configs/skills.json +26 -0
  6. package/dist/helpers/add/hooks-config.d.ts.map +1 -1
  7. package/dist/helpers/add/hooks-config.js +0 -6
  8. package/dist/helpers/add/hooks-config.js.map +1 -1
  9. package/dist/helpers/create/copy-template.d.ts +1 -1
  10. package/dist/helpers/create/copy-template.d.ts.map +1 -1
  11. package/dist/helpers/create/index.d.ts.map +1 -1
  12. package/dist/helpers/create/index.js +2 -1
  13. package/dist/helpers/create/index.js.map +1 -1
  14. package/dist/helpers/create/setup-agent.d.ts.map +1 -1
  15. package/dist/helpers/create/setup-agent.js +15 -5
  16. package/dist/helpers/create/setup-agent.js.map +1 -1
  17. package/dist/helpers/integrate/merge-config.d.ts.map +1 -1
  18. package/dist/helpers/integrate/merge-config.js +1 -2
  19. package/dist/helpers/integrate/merge-config.js.map +1 -1
  20. package/dist/helpers/integrate/sanity/config.d.ts.map +1 -1
  21. package/dist/helpers/integrate/sanity/config.js +5 -10
  22. package/dist/helpers/integrate/sanity/config.js.map +1 -1
  23. package/dist/helpers/integrate/sanity/mergers/layout-merger.d.ts.map +1 -1
  24. package/dist/helpers/integrate/sanity/mergers/layout-merger.js +13 -12
  25. package/dist/helpers/integrate/sanity/mergers/layout-merger.js.map +1 -1
  26. package/dist/helpers/skills/index.d.ts +10 -0
  27. package/dist/helpers/skills/index.d.ts.map +1 -0
  28. package/dist/helpers/skills/index.js +136 -0
  29. package/dist/helpers/skills/index.js.map +1 -0
  30. package/dist/index.js +102 -35
  31. package/dist/index.js.map +1 -1
  32. package/package.json +3 -2
  33. package/src/helpers/integrate/sanity/files/app/api/blog/[slug]/route.ts +2 -1
  34. package/src/helpers/integrate/sanity/files/lib/integrations/sanity/confirm-publish-action.ts +31 -0
  35. package/src/helpers/integrate/sanity/files/lib/integrations/sanity/sanity.config.ts +17 -0
  36. package/src/helpers/integrate/sanity/files/lib/utils/json-ld.tsx +249 -0
  37. package/src/template-hooks/config.js +0 -6
  38. package/src/templates/next-default/app/layout.tsx +18 -0
  39. package/src/templates/next-default/lib/hooks/use-device-detection.ts +1 -1
  40. package/src/templates/next-default/lib/hooks/use-media-breakpoint.ts +1 -1
  41. package/src/templates/next-default/lib/hooks/use-media.ts +29 -0
  42. package/src/templates/next-default/lib/utils/json-ld.tsx +199 -0
  43. package/src/templates/next-default/package.json +1 -1
  44. package/src/templates/next-default/tsconfig.json +1 -0
  45. package/src/templates/next-experiments/app/layout.tsx +18 -0
  46. package/src/templates/next-experiments/lib/hooks/use-device-detection.ts +1 -1
  47. package/src/templates/next-experiments/lib/hooks/use-media-breakpoint.ts +1 -1
  48. package/src/templates/next-experiments/lib/hooks/use-media.ts +29 -0
  49. package/src/templates/next-experiments/lib/utils/json-ld.tsx +199 -0
  50. package/src/templates/next-experiments/package.json +1 -1
  51. package/src/templates/next-experiments/tsconfig.json +1 -0
  52. package/src/templates/next-pagebuilder/.env.example +11 -0
  53. package/src/templates/next-pagebuilder/README.md +23 -0
  54. package/src/templates/next-pagebuilder/_gitignore +67 -0
  55. package/src/templates/next-pagebuilder/app/(content)/[[...slug]]/page.tsx +68 -0
  56. package/src/templates/next-pagebuilder/app/(content)/layout.tsx +13 -0
  57. package/src/templates/next-pagebuilder/app/api/[[...slug]]/route.ts +100 -0
  58. package/src/templates/next-pagebuilder/app/api/draft-mode/disable/route.ts +7 -0
  59. package/src/templates/next-pagebuilder/app/api/draft-mode/enable/route.ts +20 -0
  60. package/src/templates/next-pagebuilder/app/api/revalidate/route.ts +121 -0
  61. package/src/templates/next-pagebuilder/app/favicon.ico +0 -0
  62. package/src/templates/next-pagebuilder/app/layout.tsx +80 -0
  63. package/src/templates/next-pagebuilder/app/robots.ts +15 -0
  64. package/src/templates/next-pagebuilder/app/sitemap.md/route.ts +124 -0
  65. package/src/templates/next-pagebuilder/app/sitemap.xml/route.ts +80 -0
  66. package/src/templates/next-pagebuilder/app/studio/[[...tool]]/page.tsx +8 -0
  67. package/src/templates/next-pagebuilder/biome.json +239 -0
  68. package/src/templates/next-pagebuilder/components/layout/footer/index.tsx +95 -0
  69. package/src/templates/next-pagebuilder/components/layout/header/components/cta-button.tsx +28 -0
  70. package/src/templates/next-pagebuilder/components/layout/header/components/mega-menu-panel.tsx +90 -0
  71. package/src/templates/next-pagebuilder/components/layout/header/components/nav-item-renderer.tsx +98 -0
  72. package/src/templates/next-pagebuilder/components/layout/header/components/nav-leaf-item.tsx +33 -0
  73. package/src/templates/next-pagebuilder/components/layout/header/components/types.ts +7 -0
  74. package/src/templates/next-pagebuilder/components/layout/header/header-client.tsx +110 -0
  75. package/src/templates/next-pagebuilder/components/layout/header/index.tsx +8 -0
  76. package/src/templates/next-pagebuilder/components/layout/json-ld/index.tsx +45 -0
  77. package/src/templates/next-pagebuilder/components/layout/wrapper/index.tsx +30 -0
  78. package/src/templates/next-pagebuilder/components/page-builder/components/article-content/index.tsx +83 -0
  79. package/src/templates/next-pagebuilder/components/page-builder/components/article-content/related-post-item.tsx +27 -0
  80. package/src/templates/next-pagebuilder/components/page-builder/components/description.tsx +17 -0
  81. package/src/templates/next-pagebuilder/components/page-builder/components/hero.tsx +17 -0
  82. package/src/templates/next-pagebuilder/components/page-builder/components/post-collection/content-card.tsx +66 -0
  83. package/src/templates/next-pagebuilder/components/page-builder/components/post-collection/content-grid.tsx +42 -0
  84. package/src/templates/next-pagebuilder/components/page-builder/components/post-collection/index.tsx +28 -0
  85. package/src/templates/next-pagebuilder/components/page-builder/components/post-collection/types.ts +16 -0
  86. package/src/templates/next-pagebuilder/components/page-builder/renderer.tsx +36 -0
  87. package/src/templates/next-pagebuilder/components/page-builder/types.ts +23 -0
  88. package/src/templates/next-pagebuilder/components/page-document/index.tsx +91 -0
  89. package/src/templates/next-pagebuilder/components/sanity/draft-mode-toggle.tsx +27 -0
  90. package/src/templates/next-pagebuilder/components/sanity/rich-text.tsx +87 -0
  91. package/src/templates/next-pagebuilder/components/sanity/visual-editing.tsx +27 -0
  92. package/src/templates/next-pagebuilder/components/ui/image/index.tsx +216 -0
  93. package/src/templates/next-pagebuilder/components/ui/link/index.tsx +152 -0
  94. package/src/templates/next-pagebuilder/components/ui/sanity-image/index.tsx +41 -0
  95. package/src/templates/next-pagebuilder/lib/integrations/check-integration.ts +5 -0
  96. package/src/templates/next-pagebuilder/lib/integrations/sanity/client.ts +27 -0
  97. package/src/templates/next-pagebuilder/lib/integrations/sanity/components/disable-draft-mode.tsx +23 -0
  98. package/src/templates/next-pagebuilder/lib/integrations/sanity/components/page-builder-input.tsx +36 -0
  99. package/src/templates/next-pagebuilder/lib/integrations/sanity/components/page-category-input.tsx +50 -0
  100. package/src/templates/next-pagebuilder/lib/integrations/sanity/components/rich-text.tsx +84 -0
  101. package/src/templates/next-pagebuilder/lib/integrations/sanity/confirm-publish-action.ts +40 -0
  102. package/src/templates/next-pagebuilder/lib/integrations/sanity/env.ts +34 -0
  103. package/src/templates/next-pagebuilder/lib/integrations/sanity/fetchers/layout.ts +35 -0
  104. package/src/templates/next-pagebuilder/lib/integrations/sanity/icons.ts +58 -0
  105. package/src/templates/next-pagebuilder/lib/integrations/sanity/live/index.tsx +61 -0
  106. package/src/templates/next-pagebuilder/lib/integrations/sanity/markdown-proxy.config.ts +50 -0
  107. package/src/templates/next-pagebuilder/lib/integrations/sanity/page-builder-config.ts +132 -0
  108. package/src/templates/next-pagebuilder/lib/integrations/sanity/page-category.ts +28 -0
  109. package/src/templates/next-pagebuilder/lib/integrations/sanity/queries.ts +281 -0
  110. package/src/templates/next-pagebuilder/lib/integrations/sanity/sanity.cli.ts +29 -0
  111. package/src/templates/next-pagebuilder/lib/integrations/sanity/sanity.config.ts +211 -0
  112. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/components/index.ts +4 -0
  113. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/components/reusable/blog-content.ts +89 -0
  114. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/components/reusable/description.ts +29 -0
  115. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/components/reusable/hero.ts +28 -0
  116. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/components/singleton/content-collection.ts +45 -0
  117. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/content/author.ts +70 -0
  118. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/content/blog-category.ts +55 -0
  119. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/index.ts +96 -0
  120. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/layout/company-data.ts +62 -0
  121. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/layout/footer.ts +79 -0
  122. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/layout/navbar.ts +74 -0
  123. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/shared/link.ts +125 -0
  124. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/shared/logo-field.ts +9 -0
  125. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/shared/metadata.ts +68 -0
  126. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/shared/nav-objects.ts +192 -0
  127. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/shared/page-builder.ts +39 -0
  128. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/shared/page-folder.ts +124 -0
  129. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/shared/page.ts +232 -0
  130. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/shared/richText.ts +63 -0
  131. package/src/templates/next-pagebuilder/lib/integrations/sanity/singletons.ts +44 -0
  132. package/src/templates/next-pagebuilder/lib/integrations/sanity/structure.ts +453 -0
  133. package/src/templates/next-pagebuilder/lib/integrations/sanity/utils/image.ts +8 -0
  134. package/src/templates/next-pagebuilder/lib/integrations/sanity/utils/link.ts +137 -0
  135. package/src/templates/next-pagebuilder/lib/integrations/sanity/utils/page-builder-markdown.ts +81 -0
  136. package/src/templates/next-pagebuilder/lib/scripts/sanity-typegen.ts +45 -0
  137. package/src/templates/next-pagebuilder/lib/styles/cn.ts +5 -0
  138. package/src/templates/next-pagebuilder/lib/styles/global.css +70 -0
  139. package/src/templates/next-pagebuilder/lib/utils/base-url.ts +17 -0
  140. package/src/templates/next-pagebuilder/lib/utils/format-date.ts +8 -0
  141. package/src/templates/next-pagebuilder/lib/utils/json-ld.tsx +213 -0
  142. package/src/templates/next-pagebuilder/lib/utils/metadata.ts +167 -0
  143. package/src/templates/next-pagebuilder/lib/utils/sitemap.ts +37 -0
  144. package/src/templates/next-pagebuilder/lib/utils/slug-tag.ts +6 -0
  145. package/src/templates/next-pagebuilder/next.config.ts +134 -0
  146. package/src/templates/next-pagebuilder/package.json +71 -0
  147. package/src/templates/next-pagebuilder/postcss.config.mjs +39 -0
  148. package/src/templates/next-pagebuilder/proxy.ts +81 -0
  149. package/src/templates/next-pagebuilder/svg.d.ts +5 -0
  150. package/src/templates/next-pagebuilder/tsconfig.json +38 -0
  151. package/src/templates/next-webgl/app/layout.tsx +18 -0
  152. package/src/templates/next-webgl/lib/hooks/use-device-detection.ts +1 -1
  153. package/src/templates/next-webgl/lib/hooks/use-media-breakpoint.ts +1 -1
  154. package/src/templates/next-webgl/lib/hooks/use-media.ts +29 -0
  155. package/src/templates/next-webgl/lib/utils/json-ld.tsx +199 -0
  156. package/src/templates/next-webgl/package.json +1 -1
  157. package/src/templates/next-webgl/tsconfig.json +1 -0
  158. package/plugins/no-anchor-element.grit +0 -11
  159. package/plugins/no-relative-parent-imports.grit +0 -6
  160. package/plugins/no-unnecessary-forwardref.grit +0 -5
  161. package/src/helpers/integrate/sanity/files/lib/scripts/copy-sanity-mcp.ts +0 -23
  162. package/src/helpers/integrate/sanity/files/lib/scripts/generate-page.ts +0 -297
  163. package/src/template-hooks/use-media.ts +0 -33
@@ -0,0 +1,45 @@
1
+ import { defineField, defineType } from "sanity"
2
+ import type { SanityStudioIcon } from "@/lib/integrations/sanity/icons"
3
+ import { pageListIcon } from "@/lib/integrations/sanity/icons"
4
+
5
+ function createContentCollection(config: {
6
+ name: string
7
+ title: string
8
+ icon: SanityStudioIcon
9
+ label: string
10
+ }) {
11
+ return defineType({
12
+ name: config.name,
13
+ title: config.title,
14
+ type: "document",
15
+ icon: config.icon,
16
+ description: `This component contains all the ${config.label}`,
17
+ fields: [
18
+ defineField({
19
+ name: "subtitle",
20
+ title: "Subtitle",
21
+ type: "string",
22
+ description:
23
+ "Text displayed between the featured articles and the rest of the collection",
24
+ }),
25
+ ],
26
+ preview: {
27
+ select: {
28
+ title: "title",
29
+ },
30
+ prepare({ title }) {
31
+ return {
32
+ title: title || config.title,
33
+ subtitle: `${config.title} with search and category filters`,
34
+ }
35
+ },
36
+ },
37
+ })
38
+ }
39
+
40
+ export const blogCollection = createContentCollection({
41
+ name: "blogCollection",
42
+ title: "Blog Collection",
43
+ icon: pageListIcon,
44
+ label: "blog articles",
45
+ })
@@ -0,0 +1,70 @@
1
+ import { defineField, defineType } from "sanity"
2
+ import { authorIcon } from "@/lib/integrations/sanity/icons"
3
+
4
+ export const author = defineType({
5
+ name: "author",
6
+ title: "Author",
7
+ type: "document",
8
+ icon: authorIcon,
9
+ fields: [
10
+ defineField({
11
+ name: "name",
12
+ title: "Name",
13
+ type: "string",
14
+ validation: (Rule) => Rule.required(),
15
+ }),
16
+ defineField({
17
+ name: "avatar",
18
+ title: "Avatar",
19
+ type: "image",
20
+ options: {
21
+ hotspot: true,
22
+ },
23
+ fields: [
24
+ {
25
+ name: "alt",
26
+ title: "Alt Text",
27
+ type: "string",
28
+ description: "Alternative text for screen readers",
29
+ },
30
+ ],
31
+ validation: (Rule) => Rule.required(),
32
+ }),
33
+ defineField({
34
+ name: "role",
35
+ title: "Role",
36
+ type: "string",
37
+ description: "Job title or role (e.g. CEO, Engineering Lead)",
38
+ }),
39
+ defineField({
40
+ name: "description",
41
+ title: "Description",
42
+ type: "text",
43
+ rows: 3,
44
+ description: "Short bio displayed on blog articles",
45
+ }),
46
+ defineField({
47
+ name: "linkedInUrl",
48
+ title: "LinkedIn URL",
49
+ type: "url",
50
+ validation: (Rule) =>
51
+ Rule.uri({
52
+ scheme: ["https"],
53
+ }),
54
+ }),
55
+ ],
56
+ preview: {
57
+ select: {
58
+ title: "name",
59
+ subtitle: "role",
60
+ media: "avatar",
61
+ },
62
+ },
63
+ orderings: [
64
+ {
65
+ title: "Name A-Z",
66
+ name: "nameAsc",
67
+ by: [{ field: "name", direction: "asc" }],
68
+ },
69
+ ],
70
+ })
@@ -0,0 +1,55 @@
1
+ import { defineField, defineType } from "sanity"
2
+ import { blogCategoryIcon } from "@/lib/integrations/sanity/icons"
3
+
4
+ export const blogCategory = defineType({
5
+ name: "blogCategory",
6
+ title: "Blog Category",
7
+ type: "document",
8
+ icon: blogCategoryIcon,
9
+ fields: [
10
+ defineField({
11
+ name: "title",
12
+ title: "Title",
13
+ type: "string",
14
+ description: "Category name (e.g. AI, Product Updates, Engineering)",
15
+ validation: (Rule) => Rule.required(),
16
+ }),
17
+ defineField({
18
+ name: "slug",
19
+ title: "Slug",
20
+ type: "slug",
21
+ description: "URL-friendly identifier used for filtering",
22
+ options: {
23
+ source: "title",
24
+ maxLength: 96,
25
+ },
26
+ validation: (Rule) =>
27
+ Rule.required().custom((slug) => {
28
+ if (!slug?.current) return "Required"
29
+ if (!/^[a-z0-9-]+$/.test(slug.current)) {
30
+ return "Slug must be lowercase with hyphens only"
31
+ }
32
+ return true
33
+ }),
34
+ }),
35
+ defineField({
36
+ name: "description",
37
+ title: "Description",
38
+ type: "text",
39
+ rows: 2,
40
+ description: "Brief description of this category",
41
+ }),
42
+ ],
43
+ preview: {
44
+ select: {
45
+ title: "title",
46
+ },
47
+ },
48
+ orderings: [
49
+ {
50
+ title: "Title A-Z",
51
+ name: "titleAsc",
52
+ by: [{ field: "title", direction: "asc" }],
53
+ },
54
+ ],
55
+ })
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Sanity Schema Types
3
+ *
4
+ * All schema definitions for Sanity CMS.
5
+ * Shared schemas live in `schemas/shared`, content documents live in
6
+ * `schemas/content`, component documents live in `schemas/components`, and
7
+ * layout documents live in `schemas/layout`.
8
+ */
9
+
10
+ import type { SchemaTypeDefinition } from "sanity"
11
+ import {
12
+ blogCollection,
13
+ blogContent,
14
+ description,
15
+ hero,
16
+ } from "./components"
17
+ import { author } from "./content/author"
18
+ import { blogCategory } from "./content/blog-category"
19
+ import { companyData } from "./layout/company-data"
20
+ import { footer } from "./layout/footer"
21
+ import { navbar } from "./layout/navbar"
22
+ import { externalLink, link, pageReference } from "./shared/link"
23
+ import { metadata } from "./shared/metadata"
24
+ import {
25
+ navColumn,
26
+ navItem,
27
+ navLeafItem,
28
+ navMegaMenu,
29
+ } from "./shared/nav-objects"
30
+ import { page } from "./shared/page"
31
+ import { pageBuilder } from "./shared/page-builder"
32
+ import { pageFolder } from "./shared/page-folder"
33
+ import { richText } from "./shared/richText"
34
+
35
+ // Re-export all schemas for convenience
36
+ export {
37
+ author,
38
+ blogCategory,
39
+ blogCollection,
40
+ blogContent,
41
+ companyData,
42
+ description,
43
+ externalLink,
44
+ footer,
45
+ hero,
46
+ link,
47
+ metadata,
48
+ navbar,
49
+ navColumn,
50
+ navItem,
51
+ navLeafItem,
52
+ navMegaMenu,
53
+ page,
54
+ pageBuilder,
55
+ pageFolder,
56
+ pageReference,
57
+ richText,
58
+ }
59
+
60
+ // Schema collection for Sanity configuration
61
+ export const schema: { types: SchemaTypeDefinition[] } = {
62
+ types: [
63
+ // Object types (reusable field types)
64
+ pageReference,
65
+ externalLink,
66
+ link,
67
+ metadata,
68
+ pageBuilder,
69
+ richText,
70
+
71
+ // Navigation object types (used by navbar)
72
+ navLeafItem,
73
+ navColumn,
74
+ navMegaMenu,
75
+ navItem,
76
+
77
+ // Component types (reusable document components)
78
+ blogCollection,
79
+ blogContent,
80
+ hero,
81
+ description,
82
+
83
+ // Layout types (singletons for site-wide layout)
84
+ companyData,
85
+ navbar,
86
+ footer,
87
+
88
+ // Document types (content pages)
89
+ page,
90
+ pageFolder,
91
+
92
+ // Content types (reusable content)
93
+ author,
94
+ blogCategory,
95
+ ],
96
+ }
@@ -0,0 +1,62 @@
1
+ import { defineArrayMember, defineField, defineType } from "sanity"
2
+ import { socialLinksIcon } from "@/lib/integrations/sanity/icons"
3
+
4
+ export const companyData = defineType({
5
+ name: "companyData",
6
+ title: "Company Data",
7
+ type: "document",
8
+ fields: [
9
+ defineField({
10
+ name: "socialLinks",
11
+ title: "Social Links",
12
+ type: "array",
13
+ of: [
14
+ defineArrayMember({
15
+ type: "object",
16
+ icon: socialLinksIcon,
17
+ fields: [
18
+ defineField({
19
+ name: "name",
20
+ title: "Name",
21
+ type: "string",
22
+ description: "Platform name (e.g. LinkedIn, TikTok, Bluesky)",
23
+ validation: (Rule) => Rule.required(),
24
+ }),
25
+ defineField({
26
+ name: "icon",
27
+ title: "Icon",
28
+ type: "image",
29
+ description: "Platform icon (SVG, PNG, WebP, etc.)",
30
+ validation: (Rule) => Rule.required(),
31
+ }),
32
+ defineField({
33
+ name: "url",
34
+ title: "URL",
35
+ type: "url",
36
+ validation: (Rule) => Rule.required(),
37
+ }),
38
+ defineField({
39
+ name: "label",
40
+ title: "Label",
41
+ type: "string",
42
+ description:
43
+ "Accessible label for the link (e.g. 'Follow us on LinkedIn')",
44
+ }),
45
+ ],
46
+ preview: {
47
+ select: {
48
+ title: "name",
49
+ subtitle: "url",
50
+ media: "icon",
51
+ },
52
+ },
53
+ }),
54
+ ],
55
+ }),
56
+ ],
57
+ preview: {
58
+ prepare() {
59
+ return { title: "Company Data" }
60
+ },
61
+ },
62
+ })
@@ -0,0 +1,79 @@
1
+ import { defineField, defineType } from "sanity"
2
+
3
+ export const footer = defineType({
4
+ name: "footer",
5
+ title: "Footer",
6
+ type: "document",
7
+ fields: [
8
+ defineField({
9
+ name: "links",
10
+ title: "Links",
11
+ description: "Navigation columns (e.g. Products, Solutions, Resources)",
12
+ type: "array",
13
+ of: [
14
+ {
15
+ type: "object",
16
+ fields: [
17
+ defineField({
18
+ name: "title",
19
+ title: "Title",
20
+ type: "string",
21
+ validation: (Rule) => Rule.required(),
22
+ }),
23
+ defineField({
24
+ name: "items",
25
+ title: "Items",
26
+ type: "array",
27
+ of: [
28
+ {
29
+ type: "object",
30
+ fields: [
31
+ defineField({
32
+ name: "label",
33
+ title: "Label",
34
+ type: "string",
35
+ validation: (Rule) => Rule.required(),
36
+ }),
37
+ defineField({
38
+ name: "href",
39
+ title: "Path",
40
+ type: "string",
41
+ description: "Relative path (e.g. /products, /about)",
42
+ validation: (Rule) =>
43
+ Rule.required().custom((value) => {
44
+ if (
45
+ typeof value === "string" &&
46
+ !value.startsWith("/")
47
+ ) {
48
+ return "Path must start with /"
49
+ }
50
+ return true
51
+ }),
52
+ }),
53
+ ],
54
+ preview: {
55
+ select: { title: "label", subtitle: "href" },
56
+ },
57
+ },
58
+ ],
59
+ }),
60
+ ],
61
+ preview: {
62
+ select: { title: "title", items: "items" },
63
+ prepare({ title, items }) {
64
+ return {
65
+ title,
66
+ subtitle: `${items?.length ?? 0} items`,
67
+ }
68
+ },
69
+ },
70
+ },
71
+ ],
72
+ }),
73
+ ],
74
+ preview: {
75
+ prepare() {
76
+ return { title: "Footer" }
77
+ },
78
+ },
79
+ })
@@ -0,0 +1,74 @@
1
+ import { defineArrayMember, defineField, defineType } from "sanity"
2
+
3
+ import { logoField } from "@/lib/integrations/sanity/schemas/shared/logo-field"
4
+
5
+ export const navbar = defineType({
6
+ name: "navbar",
7
+ title: "Navbar",
8
+ type: "document",
9
+ fields: [
10
+ logoField,
11
+ defineField({
12
+ name: "navigationItems",
13
+ title: "Navigation Items",
14
+ type: "array",
15
+ of: [defineArrayMember({ type: "navItem" })],
16
+ description: "Top-level navigation items -- direct links or menu triggers",
17
+ }),
18
+ defineField({
19
+ name: "ctaButtons",
20
+ title: "CTA Buttons",
21
+ type: "array",
22
+ of: [
23
+ {
24
+ type: "object",
25
+ fields: [
26
+ defineField({
27
+ name: "label",
28
+ title: "Label",
29
+ type: "string",
30
+ validation: (Rule) => Rule.required(),
31
+ }),
32
+ defineField({
33
+ name: "link",
34
+ title: "Link",
35
+ type: "link",
36
+ validation: (Rule) => Rule.required(),
37
+ }),
38
+ defineField({
39
+ name: "variant",
40
+ title: "Variant",
41
+ type: "string",
42
+ options: {
43
+ list: [
44
+ { title: "Primary", value: "primary" },
45
+ { title: "Secondary", value: "secondary" },
46
+ ],
47
+ },
48
+ initialValue: "primary",
49
+ validation: (Rule) => Rule.required(),
50
+ }),
51
+ ],
52
+ preview: {
53
+ select: {
54
+ title: "label",
55
+ variant: "variant",
56
+ },
57
+ prepare({ title, variant }) {
58
+ return {
59
+ title,
60
+ subtitle: variant || "primary",
61
+ }
62
+ },
63
+ },
64
+ },
65
+ ],
66
+ description: "Action buttons on the right side of the navbar",
67
+ }),
68
+ ],
69
+ preview: {
70
+ prepare() {
71
+ return { title: "Navbar" }
72
+ },
73
+ },
74
+ })
@@ -0,0 +1,125 @@
1
+ import { defineArrayMember, defineField, defineType } from "sanity"
2
+
3
+ /**
4
+ * Page Reference -- internal link to a Sanity page document.
5
+ */
6
+ export const pageReference = defineType({
7
+ name: "pageReference",
8
+ title: "Page",
9
+ type: "object",
10
+ fields: [
11
+ defineField({
12
+ name: "page",
13
+ title: "Page",
14
+ type: "reference",
15
+ to: [{ type: "page" }],
16
+ validation: (Rule) => Rule.required(),
17
+ }),
18
+ defineField({
19
+ name: "openInNewTab",
20
+ title: "Open in New Tab",
21
+ type: "boolean",
22
+ initialValue: false,
23
+ }),
24
+ ],
25
+ preview: {
26
+ select: {
27
+ text: "text",
28
+ pageTitle: "page.title",
29
+ },
30
+ prepare({ text, pageTitle }) {
31
+ return {
32
+ title: text || pageTitle || "Untitled",
33
+ subtitle: pageTitle ? `Page: ${pageTitle}` : "Page Reference",
34
+ }
35
+ },
36
+ },
37
+ })
38
+
39
+ /**
40
+ * External Link -- URL pointing outside the site (or to pages not yet in Sanity).
41
+ */
42
+ export const externalLink = defineType({
43
+ name: "externalLink",
44
+ title: "URL",
45
+ type: "object",
46
+ fields: [
47
+ defineField({
48
+ name: "href",
49
+ title: "URL",
50
+ type: "url",
51
+ validation: (Rule) =>
52
+ Rule.required().uri({
53
+ scheme: ["http", "https", "mailto", "tel"],
54
+ allowRelative: true,
55
+ }),
56
+ }),
57
+ defineField({
58
+ name: "openInNewTab",
59
+ title: "Open in New Tab",
60
+ type: "boolean",
61
+ initialValue: false,
62
+ }),
63
+ ],
64
+ preview: {
65
+ select: {
66
+ text: "text",
67
+ href: "href",
68
+ },
69
+ prepare({ text, href }) {
70
+ return {
71
+ title: text || href || "Untitled",
72
+ subtitle: href ? `URL: ${href}` : "External Link",
73
+ }
74
+ },
75
+ },
76
+ })
77
+
78
+ /**
79
+ * Link -- array-of-one wrapper that accepts either a page reference or external URL.
80
+ * Editors click "Add" and pick which type they need.
81
+ */
82
+ export const link = defineType({
83
+ name: "link",
84
+ title: "Link",
85
+ type: "array",
86
+ of: [
87
+ defineArrayMember({ type: "pageReference" }),
88
+ defineArrayMember({ type: "externalLink" }),
89
+ ],
90
+ validation: (Rule) => Rule.max(1),
91
+ })
92
+
93
+ /**
94
+ * Annotations for Portable Text -- both link types as separate mark annotations.
95
+ */
96
+ export const linkAnnotations = [
97
+ defineArrayMember({ type: "pageReference" }),
98
+ defineArrayMember({ type: "externalLink" }),
99
+ ]
100
+
101
+ // Helper field exports for easy reuse
102
+ export const linkField = defineField({
103
+ name: "link",
104
+ title: "Link",
105
+ type: "link",
106
+ })
107
+
108
+ type LinkFieldOptions = {
109
+ name?: string
110
+ title?: string
111
+ required?: boolean
112
+ }
113
+
114
+ export function extendedLinkField({
115
+ name = "link",
116
+ title = "Link",
117
+ required = false,
118
+ }: LinkFieldOptions = {}) {
119
+ return defineField({
120
+ name,
121
+ title,
122
+ type: "link",
123
+ validation: required ? (Rule) => Rule.required().min(1) : undefined,
124
+ })
125
+ }
@@ -0,0 +1,9 @@
1
+ import { defineField } from "sanity"
2
+
3
+ export const logoField = defineField({
4
+ name: "logo",
5
+ title: "Logo",
6
+ type: "image",
7
+ options: { hotspot: true },
8
+ validation: (Rule) => Rule.required(),
9
+ })
@@ -0,0 +1,68 @@
1
+ import { defineField, defineType } from "sanity"
2
+
3
+ export const metadata = defineType({
4
+ name: "metadata",
5
+ title: "SEO & Metadata",
6
+ type: "object",
7
+ options: {
8
+ collapsible: true,
9
+ collapsed: true,
10
+ },
11
+ fields: [
12
+ defineField({
13
+ name: "metaTitle",
14
+ title: "Meta Title",
15
+ type: "string",
16
+ description: "Title used by search engines",
17
+ validation: (Rule) =>
18
+ Rule.max(60).warning(
19
+ "Title should be under 60 characters for optimal SEO"
20
+ ),
21
+ }),
22
+ defineField({
23
+ name: "metaDescription",
24
+ title: "Meta Description",
25
+ type: "text",
26
+ rows: 3,
27
+ description: "Description used by search engines",
28
+ validation: (Rule) =>
29
+ Rule.max(160).warning(
30
+ "Description should be under 160 characters for optimal SEO"
31
+ ),
32
+ }),
33
+ defineField({
34
+ name: "image",
35
+ title: "OG Image",
36
+ type: "image",
37
+ description: "Image for social sharing previews (1200x630px recommended)",
38
+ options: {
39
+ hotspot: true,
40
+ },
41
+ }),
42
+ defineField({
43
+ name: "index",
44
+ title: "Index",
45
+ type: "boolean",
46
+ description: "Allow search engines to index this page",
47
+ initialValue: true,
48
+ }),
49
+ defineField({
50
+ name: "title",
51
+ title: "Legacy Meta Title",
52
+ type: "string",
53
+ hidden: true,
54
+ }),
55
+ defineField({
56
+ name: "description",
57
+ title: "Legacy Meta Description",
58
+ type: "text",
59
+ hidden: true,
60
+ }),
61
+ defineField({
62
+ name: "noIndex",
63
+ title: "Legacy No Index",
64
+ type: "boolean",
65
+ hidden: true,
66
+ }),
67
+ ],
68
+ })