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,239 @@
1
+ {
2
+ "root": true,
3
+ "$schema": "node_modules/@biomejs/biome/configuration_schema.json",
4
+ "assist": {
5
+ "actions": {
6
+ "source": {
7
+ "organizeImports": "on"
8
+ }
9
+ }
10
+ },
11
+ "css": {
12
+ "formatter": {
13
+ "enabled": true
14
+ },
15
+ "linter": {
16
+ "enabled": true
17
+ },
18
+ "parser": {
19
+ "cssModules": true,
20
+ "tailwindDirectives": true
21
+ }
22
+ },
23
+ "files": {
24
+ "ignoreUnknown": true,
25
+ "includes": [
26
+ "**",
27
+ "!node_modules",
28
+ "!**/.next",
29
+ "!**/dist",
30
+ "!**/public",
31
+ "!.github",
32
+ "!.vercel",
33
+ "!pnpm-lock.yaml",
34
+ "!bun.lock",
35
+ "!**/*.md",
36
+ "!**/*.mdx",
37
+ "!**/tailwind.css",
38
+ "!**/*.grit"
39
+ ]
40
+ },
41
+ "formatter": {
42
+ "enabled": true,
43
+ "indentStyle": "space",
44
+ "indentWidth": 2,
45
+ "lineEnding": "lf",
46
+ "lineWidth": 80
47
+ },
48
+ "javascript": {
49
+ "formatter": {
50
+ "enabled": true,
51
+ "quoteStyle": "double",
52
+ "semicolons": "asNeeded",
53
+ "trailingCommas": "es5"
54
+ }
55
+ },
56
+ "json": {
57
+ "parser": {
58
+ "allowComments": true
59
+ }
60
+ },
61
+ "linter": {
62
+ "domains": {
63
+ "next": "recommended",
64
+ "react": "recommended"
65
+ },
66
+ "enabled": true,
67
+ "rules": {
68
+ "a11y": {
69
+ "noAriaUnsupportedElements": "error",
70
+ "noAutofocus": "warn",
71
+ "noDistractingElements": "error",
72
+ "noRedundantAlt": "error",
73
+ "useAltText": "error",
74
+ "useButtonType": "error",
75
+ "useKeyWithClickEvents": "warn",
76
+ "useSemanticElements": "warn",
77
+ "useValidAnchor": "warn",
78
+ "useValidAriaProps": "error",
79
+ "useValidAriaRole": "error",
80
+ "useValidAriaValues": "error"
81
+ },
82
+ "complexity": {
83
+ "noForEach": "off",
84
+ "useFlatMap": "warn",
85
+ "useSimplifiedLogicExpression": "warn"
86
+ },
87
+ "correctness": {
88
+ "noInvalidUseBeforeDeclaration": "error",
89
+ "noUnknownMediaFeatureName": "off",
90
+ "noUnusedFunctionParameters": "warn",
91
+ "noUnusedImports": "error",
92
+ "noUnusedVariables": "error",
93
+ "useExhaustiveDependencies": "warn"
94
+ },
95
+ "nursery": {
96
+ "useSortedClasses": {
97
+ "level": "warn",
98
+ "options": {
99
+ "attributes": ["class", "className"],
100
+ "functions": ["cn", "cva"]
101
+ }
102
+ }
103
+ },
104
+ "performance": {
105
+ "noImgElement": "error"
106
+ },
107
+ "recommended": true,
108
+ "security": {
109
+ "noDangerouslySetInnerHtml": "warn",
110
+ "noDangerouslySetInnerHtmlWithChildren": "error",
111
+ "noGlobalEval": "error"
112
+ },
113
+ "style": {
114
+ "noInferrableTypes": "error",
115
+ "noNestedTernary": "error",
116
+ "noNonNullAssertion": "off",
117
+ "noParameterAssign": "error",
118
+ "noUnusedTemplateLiteral": "off",
119
+ "noUselessElse": "error",
120
+ "useAsConstAssertion": "error",
121
+ "useCollapsedElseIf": "warn",
122
+ "useConsistentArrayType": "error",
123
+ "useConsistentBuiltinInstantiation": "error",
124
+ "useDefaultParameterLast": "error",
125
+ "useEnumInitializers": "error",
126
+ "useExponentiationOperator": "error",
127
+ "useFilenamingConvention": {
128
+ "level": "warn",
129
+ "options": {
130
+ "filenameCases": ["kebab-case", "camelCase"],
131
+ "strictCase": false
132
+ }
133
+ },
134
+ "useForOf": "warn",
135
+ "useNumberNamespace": "error",
136
+ "useSelfClosingElements": "error",
137
+ "useShorthandAssign": "error",
138
+ "useSingleVarDeclarator": "error",
139
+ "useTemplate": "warn"
140
+ },
141
+ "suspicious": {
142
+ "noDebugger": "warn",
143
+ "noDoubleEquals": "error",
144
+ "noEmptyBlockStatements": "warn",
145
+ "noExplicitAny": "error",
146
+ "noGlobalIsFinite": "error",
147
+ "noGlobalIsNan": "error",
148
+ "noMisleadingCharacterClass": "error",
149
+ "noPrototypeBuiltins": "warn",
150
+ "noSelfCompare": "error",
151
+ "noSparseArray": "error",
152
+ "useAwait": "off"
153
+ }
154
+ }
155
+ },
156
+ "overrides": [
157
+ {
158
+ "includes": ["**/*.css"],
159
+ "linter": {
160
+ "rules": {
161
+ "correctness": {
162
+ "noUnknownFunction": "off"
163
+ }
164
+ }
165
+ }
166
+ },
167
+ {
168
+ "includes": ["**/*.tsx", "**/*.jsx"],
169
+ "linter": {
170
+ "rules": {
171
+ "a11y": {
172
+ "useKeyWithClickEvents": "error",
173
+ "useKeyWithMouseEvents": "error",
174
+ "useValidAnchor": "error"
175
+ },
176
+ "correctness": {
177
+ "useJsxKeyInIterable": "error"
178
+ }
179
+ }
180
+ }
181
+ },
182
+ {
183
+ "includes": ["**/*.ts", "**/*.tsx"],
184
+ "linter": {
185
+ "rules": {
186
+ "correctness": {
187
+ "noUndeclaredVariables": "off"
188
+ },
189
+ "style": {
190
+ "useConsistentArrayType": "error",
191
+ "useExportType": "error",
192
+ "useImportType": "error"
193
+ }
194
+ }
195
+ }
196
+ },
197
+ {
198
+ "includes": [
199
+ "app/**/*.tsx",
200
+ "app/**/*.ts",
201
+ "app/**/*.jsx",
202
+ "app/**/*.js"
203
+ ],
204
+ "linter": {
205
+ "rules": {
206
+ "style": {
207
+ "noDefaultExport": "off"
208
+ },
209
+ "suspicious": {
210
+ "useAwait": "off"
211
+ }
212
+ }
213
+ }
214
+ },
215
+ {
216
+ "includes": ["**/*.module.css"],
217
+ "linter": {
218
+ "rules": {
219
+ "correctness": {
220
+ "noUnknownProperty": "off"
221
+ },
222
+ "style": {
223
+ "noDescendingSpecificity": "off"
224
+ }
225
+ }
226
+ }
227
+ }
228
+ ],
229
+ "plugins": [
230
+ "./.biome/plugins/no-anchor-element.grit",
231
+ "./.biome/plugins/no-unnecessary-forwardref.grit",
232
+ "./.biome/plugins/no-relative-parent-imports.grit"
233
+ ],
234
+ "vcs": {
235
+ "clientKind": "git",
236
+ "enabled": true,
237
+ "useIgnoreFile": false
238
+ }
239
+ }
@@ -0,0 +1,95 @@
1
+ import { Link } from "@/components/ui/link"
2
+ import { SanityImage } from "@/components/ui/sanity-image"
3
+ import {
4
+ type CompanyData,
5
+ type FooterData,
6
+ getCompanyData,
7
+ getFooter,
8
+ } from "@/lib/integrations/sanity/fetchers/layout"
9
+
10
+ type FooterLinkGroup = NonNullable<FooterData["links"]>[number]
11
+ type FooterLinkItem = NonNullable<FooterLinkGroup["items"]>[number]
12
+ type SocialLink = NonNullable<CompanyData["socialLinks"]>[number]
13
+
14
+ export const Footer = async () => {
15
+ const [footer, companyData] = await Promise.all([
16
+ getFooter(),
17
+ getCompanyData(),
18
+ ])
19
+
20
+ const year = new Date().getFullYear()
21
+
22
+ if (!(footer || companyData)) {
23
+ return (
24
+ <footer className="border-black/10 border-t px-6 py-3">
25
+ <p>&copy; {year} Basement</p>
26
+ </footer>
27
+ )
28
+ }
29
+
30
+ const links = footer?.links ?? []
31
+
32
+ return (
33
+ <footer className="border-black/10 border-t">
34
+ <div className="mx-auto flex max-w-7xl flex-col gap-8 px-6 py-6">
35
+ {links.length > 0 ? (
36
+ <div className="grid grid-cols-5 gap-x-8 gap-y-6 md:grid-cols-3 lg:grid-cols-5">
37
+ {links.map((linkGroup: FooterLinkGroup) => (
38
+ <div key={linkGroup._key} className="flex flex-col gap-2">
39
+ <h3>{linkGroup.title}</h3>
40
+ {linkGroup.items && linkGroup.items.length > 0 ? (
41
+ <ul className="space-y-1">
42
+ {linkGroup.items
43
+ .filter(
44
+ (item: FooterLinkItem) => item.href && item.href !== "#"
45
+ )
46
+ .map((item: FooterLinkItem) => (
47
+ <li key={item._key}>
48
+ <Link href={item.href!}>{item.label}</Link>
49
+ </li>
50
+ ))}
51
+ </ul>
52
+ ) : null}
53
+ </div>
54
+ ))}
55
+ </div>
56
+ ) : (
57
+ <Link href="/">Basement</Link>
58
+ )}
59
+
60
+ <div className="flex flex-row items-center justify-between gap-3 border-black/10 border-t pt-4">
61
+ <p className="w-fit">&copy; {year} Basement</p>
62
+
63
+ {companyData?.socialLinks && companyData.socialLinks.length > 0 ? (
64
+ <div className="flex w-fit flex-wrap items-center gap-x-4 gap-y-2">
65
+ {companyData.socialLinks
66
+ .filter((social: SocialLink) => social.url)
67
+ .map((social: SocialLink) => (
68
+ <Link
69
+ key={social._key}
70
+ href={social.url!}
71
+ aria-label={
72
+ social.label ||
73
+ `Follow us on ${social.name || "social media"}`
74
+ }
75
+ >
76
+ {social.icon ? (
77
+ <SanityImage
78
+ image={social.icon}
79
+ alt={social.name || ""}
80
+ width={20}
81
+ height={20}
82
+ maxWidth={40}
83
+ />
84
+ ) : (
85
+ <span>{social.name}</span>
86
+ )}
87
+ </Link>
88
+ ))}
89
+ </div>
90
+ ) : null}
91
+ </div>
92
+ </div>
93
+ </footer>
94
+ )
95
+ }
@@ -0,0 +1,28 @@
1
+ import { Link } from "@/components/ui/link"
2
+ import { getLinkAttributes } from "@/lib/integrations/sanity/utils/link"
3
+ import { cn } from "@/lib/styles/cn"
4
+
5
+ import type { CtaButton } from "./types"
6
+
7
+ export function CtaButtonRenderer({ cta }: { cta: CtaButton }) {
8
+ const linkAttributes = cta.link ? getLinkAttributes(cta.link) : undefined
9
+ const href = linkAttributes?.href
10
+
11
+ if (!(cta.label && href && href !== "#")) return null
12
+
13
+ return (
14
+ <Link
15
+ href={href}
16
+ target={linkAttributes?.target}
17
+ rel={linkAttributes?.rel}
18
+ className={cn(
19
+ "rounded-lg px-4 py-2 font-medium text-sm transition-opacity hover:opacity-80",
20
+ cta.variant === "primary"
21
+ ? "bg-black text-white"
22
+ : "border border-black/20 text-black"
23
+ )}
24
+ >
25
+ {cta.label}
26
+ </Link>
27
+ )
28
+ }
@@ -0,0 +1,90 @@
1
+ import { Link } from "@/components/ui/link"
2
+ import { getLinkAttributes } from "@/lib/integrations/sanity/utils/link"
3
+ import { cn } from "@/lib/styles/cn"
4
+
5
+ import { NavLeafItem } from "./nav-leaf-item"
6
+ import type { MegaMenu, MegaMenuColumn as MegaMenuColumnType } from "./types"
7
+
8
+ function ColumnHeader({
9
+ title,
10
+ href,
11
+ target,
12
+ rel,
13
+ }: {
14
+ title: string
15
+ href: string | null
16
+ target?: string | undefined
17
+ rel?: string | undefined
18
+ }) {
19
+ const className =
20
+ "mb-1 block px-3 py-2 text-xs font-semibold uppercase tracking-wider text-gray-400"
21
+
22
+ if (href) {
23
+ return (
24
+ <Link
25
+ href={href}
26
+ target={target}
27
+ rel={rel}
28
+ className={cn(className, "transition-colors hover:text-black")}
29
+ >
30
+ {title}
31
+ </Link>
32
+ )
33
+ }
34
+
35
+ return <span className={className}>{title}</span>
36
+ }
37
+
38
+ function MegaMenuColumn({ column }: { column: MegaMenuColumnType }) {
39
+ const linkAttributes = column.link
40
+ ? getLinkAttributes(column.link)
41
+ : undefined
42
+ const href = linkAttributes?.href
43
+ const items = column.items
44
+
45
+ return (
46
+ <div className="flex min-w-48 flex-col">
47
+ {column.title ? (
48
+ <ColumnHeader
49
+ title={column.title}
50
+ href={href && href !== "#" ? href : null}
51
+ target={linkAttributes?.target}
52
+ rel={linkAttributes?.rel}
53
+ />
54
+ ) : null}
55
+
56
+ {items?.length ? (
57
+ <ul className="flex flex-col">
58
+ {items.map((item) => (
59
+ <li key={item._key}>
60
+ <NavLeafItem item={item} />
61
+ </li>
62
+ ))}
63
+ </ul>
64
+ ) : null}
65
+ </div>
66
+ )
67
+ }
68
+
69
+ export function MegaMenuPanel({
70
+ megaMenu,
71
+ id,
72
+ }: {
73
+ megaMenu: MegaMenu
74
+ id: string
75
+ }) {
76
+ return (
77
+ <div
78
+ id={id}
79
+ className="absolute top-full left-1/2 z-50 w-max max-w-[90vw] -translate-x-1/2 pt-2"
80
+ >
81
+ <div className="rounded-xl border border-black/10 bg-white p-6 shadow-black/5 shadow-xl">
82
+ <div className="flex gap-8">
83
+ {megaMenu.columns?.map((col) => (
84
+ <MegaMenuColumn key={col._key} column={col} />
85
+ ))}
86
+ </div>
87
+ </div>
88
+ </div>
89
+ )
90
+ }
@@ -0,0 +1,98 @@
1
+ import { CaretDownIcon } from "@phosphor-icons/react/dist/ssr"
2
+
3
+ import { Link } from "@/components/ui/link"
4
+ import { getLinkAttributes } from "@/lib/integrations/sanity/utils/link"
5
+ import { cn } from "@/lib/styles/cn"
6
+
7
+ import { MegaMenuPanel } from "./mega-menu-panel"
8
+ import type { NavItem } from "./types"
9
+
10
+ type NavItemRendererProps = {
11
+ item: NavItem
12
+ isOpen: boolean
13
+ onToggle: () => void
14
+ triggerRef: (el: HTMLButtonElement | null) => void
15
+ }
16
+
17
+ const baseLabelClassName = "text-sm transition-colors"
18
+ const interactiveLabelClassName = cn(
19
+ baseLabelClassName,
20
+ "text-gray-600 hover:text-black"
21
+ )
22
+
23
+ function getValidHref(item: NavItem) {
24
+ const linkAttributes = item.link ? getLinkAttributes(item.link) : undefined
25
+ const href =
26
+ linkAttributes?.href && linkAttributes.href !== "#"
27
+ ? linkAttributes.href
28
+ : null
29
+
30
+ return { href, linkAttributes }
31
+ }
32
+
33
+ export function NavItemRenderer({
34
+ item,
35
+ isOpen,
36
+ onToggle,
37
+ triggerRef,
38
+ }: NavItemRendererProps) {
39
+ const panelId = `mega-menu-${item._key}`
40
+
41
+ if (item.itemType === "mega-menu" && item.megaMenu) {
42
+ const megaMenu = item.megaMenu
43
+
44
+ return (
45
+ <li className="relative">
46
+ <button
47
+ ref={triggerRef}
48
+ type="button"
49
+ onClick={onToggle}
50
+ className={cn(
51
+ "flex items-center gap-1",
52
+ baseLabelClassName,
53
+ isOpen ? "text-black" : interactiveLabelClassName
54
+ )}
55
+ aria-expanded={isOpen}
56
+ aria-haspopup="true"
57
+ aria-controls={panelId}
58
+ >
59
+ {item.title}
60
+ <CaretDownIcon
61
+ size={14}
62
+ weight="bold"
63
+ className={cn(
64
+ "transition-transform duration-200",
65
+ isOpen && "rotate-180"
66
+ )}
67
+ />
68
+ </button>
69
+ {isOpen ? <MegaMenuPanel megaMenu={megaMenu} id={panelId} /> : null}
70
+ </li>
71
+ )
72
+ }
73
+
74
+ const { href, linkAttributes } = getValidHref(item)
75
+
76
+ if (href) {
77
+ return (
78
+ <li>
79
+ <Link
80
+ href={href}
81
+ target={linkAttributes?.target}
82
+ rel={linkAttributes?.rel}
83
+ className={interactiveLabelClassName}
84
+ >
85
+ {item.title}
86
+ </Link>
87
+ </li>
88
+ )
89
+ }
90
+
91
+ return (
92
+ <li>
93
+ <span className={cn(baseLabelClassName, "cursor-default text-gray-400")}>
94
+ {item.title}
95
+ </span>
96
+ </li>
97
+ )
98
+ }
@@ -0,0 +1,33 @@
1
+ import { Link } from "@/components/ui/link"
2
+ import { getLinkAttributes } from "@/lib/integrations/sanity/utils/link"
3
+
4
+ import type { LeafItem } from "./types"
5
+
6
+ export function NavLeafItem({ item }: { item: LeafItem }) {
7
+ const linkAttributes = item.link ? getLinkAttributes(item.link) : undefined
8
+ const href = linkAttributes?.href
9
+ const label = (
10
+ <span className="text-gray-700 text-sm transition-colors group-hover/leaf:text-black">
11
+ {item.title}
12
+ </span>
13
+ )
14
+
15
+ if (href && href !== "#") {
16
+ return (
17
+ <Link
18
+ href={href}
19
+ target={linkAttributes?.target}
20
+ rel={linkAttributes?.rel}
21
+ className="block rounded-md px-3 py-2 transition-colors hover:bg-gray-50"
22
+ >
23
+ {label}
24
+ </Link>
25
+ )
26
+ }
27
+
28
+ return (
29
+ <span className="block cursor-default rounded-md px-3 py-2 opacity-50">
30
+ {label}
31
+ </span>
32
+ )
33
+ }
@@ -0,0 +1,7 @@
1
+ import type { NavbarData } from "@/lib/integrations/sanity/fetchers/layout"
2
+
3
+ export type NavItem = NonNullable<NavbarData["navigationItems"]>[number]
4
+ export type MegaMenu = NonNullable<NavItem["megaMenu"]>
5
+ export type MegaMenuColumn = NonNullable<MegaMenu["columns"]>[number]
6
+ export type LeafItem = NonNullable<MegaMenuColumn["items"]>[number]
7
+ export type CtaButton = NonNullable<NavbarData["ctaButtons"]>[number]