bsmnt 0.2.11 → 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.
- package/dist/helpers/create/copy-template.d.ts +1 -1
- package/dist/helpers/create/copy-template.d.ts.map +1 -1
- package/dist/helpers/create/index.d.ts.map +1 -1
- package/dist/helpers/create/index.js +2 -1
- package/dist/helpers/create/index.js.map +1 -1
- package/dist/helpers/integrate/merge-config.d.ts.map +1 -1
- package/dist/helpers/integrate/merge-config.js +0 -2
- package/dist/helpers/integrate/merge-config.js.map +1 -1
- package/dist/helpers/integrate/sanity/config.d.ts.map +1 -1
- package/dist/helpers/integrate/sanity/config.js +3 -14
- package/dist/helpers/integrate/sanity/config.js.map +1 -1
- package/dist/index.js +84 -35
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/templates/next-pagebuilder/.env.example +11 -0
- package/src/templates/next-pagebuilder/README.md +23 -0
- package/src/templates/next-pagebuilder/_gitignore +67 -0
- package/src/templates/next-pagebuilder/app/(content)/[[...slug]]/page.tsx +68 -0
- package/src/templates/next-pagebuilder/app/(content)/layout.tsx +13 -0
- package/src/templates/next-pagebuilder/app/api/[[...slug]]/route.ts +100 -0
- package/src/templates/next-pagebuilder/app/api/draft-mode/disable/route.ts +7 -0
- package/src/templates/next-pagebuilder/app/api/draft-mode/enable/route.ts +20 -0
- package/src/templates/next-pagebuilder/app/api/revalidate/route.ts +121 -0
- package/src/templates/next-pagebuilder/app/favicon.ico +0 -0
- package/src/templates/next-pagebuilder/app/layout.tsx +80 -0
- package/src/templates/next-pagebuilder/app/robots.ts +15 -0
- package/src/templates/next-pagebuilder/app/sitemap.md/route.ts +124 -0
- package/src/templates/next-pagebuilder/app/sitemap.xml/route.ts +80 -0
- package/src/templates/next-pagebuilder/app/studio/[[...tool]]/page.tsx +8 -0
- package/src/templates/next-pagebuilder/biome.json +239 -0
- package/src/templates/next-pagebuilder/components/layout/footer/index.tsx +95 -0
- package/src/templates/next-pagebuilder/components/layout/header/components/cta-button.tsx +28 -0
- package/src/templates/next-pagebuilder/components/layout/header/components/mega-menu-panel.tsx +90 -0
- package/src/templates/next-pagebuilder/components/layout/header/components/nav-item-renderer.tsx +98 -0
- package/src/templates/next-pagebuilder/components/layout/header/components/nav-leaf-item.tsx +33 -0
- package/src/templates/next-pagebuilder/components/layout/header/components/types.ts +7 -0
- package/src/templates/next-pagebuilder/components/layout/header/header-client.tsx +110 -0
- package/src/templates/next-pagebuilder/components/layout/header/index.tsx +8 -0
- package/src/templates/next-pagebuilder/components/layout/json-ld/index.tsx +45 -0
- package/src/templates/next-pagebuilder/components/layout/wrapper/index.tsx +30 -0
- package/src/templates/next-pagebuilder/components/page-builder/components/article-content/index.tsx +83 -0
- package/src/templates/next-pagebuilder/components/page-builder/components/article-content/related-post-item.tsx +27 -0
- package/src/templates/next-pagebuilder/components/page-builder/components/description.tsx +17 -0
- package/src/templates/next-pagebuilder/components/page-builder/components/hero.tsx +17 -0
- package/src/templates/next-pagebuilder/components/page-builder/components/post-collection/content-card.tsx +66 -0
- package/src/templates/next-pagebuilder/components/page-builder/components/post-collection/content-grid.tsx +42 -0
- package/src/templates/next-pagebuilder/components/page-builder/components/post-collection/index.tsx +28 -0
- package/src/templates/next-pagebuilder/components/page-builder/components/post-collection/types.ts +16 -0
- package/src/templates/next-pagebuilder/components/page-builder/renderer.tsx +36 -0
- package/src/templates/next-pagebuilder/components/page-builder/types.ts +23 -0
- package/src/templates/next-pagebuilder/components/page-document/index.tsx +91 -0
- package/src/templates/next-pagebuilder/components/sanity/draft-mode-toggle.tsx +27 -0
- package/src/templates/next-pagebuilder/components/sanity/rich-text.tsx +87 -0
- package/src/templates/next-pagebuilder/components/sanity/visual-editing.tsx +27 -0
- package/src/templates/next-pagebuilder/components/ui/image/index.tsx +216 -0
- package/src/templates/next-pagebuilder/components/ui/link/index.tsx +152 -0
- package/src/templates/next-pagebuilder/components/ui/sanity-image/index.tsx +41 -0
- package/src/templates/next-pagebuilder/lib/integrations/check-integration.ts +5 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/client.ts +27 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/components/disable-draft-mode.tsx +23 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/components/page-builder-input.tsx +36 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/components/page-category-input.tsx +50 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/components/rich-text.tsx +84 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/confirm-publish-action.ts +40 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/env.ts +34 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/fetchers/layout.ts +35 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/icons.ts +58 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/live/index.tsx +61 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/markdown-proxy.config.ts +50 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/page-builder-config.ts +132 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/page-category.ts +28 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/queries.ts +281 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/sanity.cli.ts +29 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/sanity.config.ts +211 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/components/index.ts +4 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/components/reusable/blog-content.ts +89 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/components/reusable/description.ts +29 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/components/reusable/hero.ts +28 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/components/singleton/content-collection.ts +45 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/content/author.ts +70 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/content/blog-category.ts +55 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/index.ts +96 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/layout/company-data.ts +62 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/layout/footer.ts +79 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/layout/navbar.ts +74 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/shared/link.ts +125 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/shared/logo-field.ts +9 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/shared/metadata.ts +68 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/shared/nav-objects.ts +192 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/shared/page-builder.ts +39 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/shared/page-folder.ts +124 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/shared/page.ts +232 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/shared/richText.ts +63 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/singletons.ts +44 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/structure.ts +453 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/utils/image.ts +8 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/utils/link.ts +137 -0
- package/src/templates/next-pagebuilder/lib/integrations/sanity/utils/page-builder-markdown.ts +81 -0
- package/src/templates/next-pagebuilder/lib/scripts/sanity-typegen.ts +45 -0
- package/src/templates/next-pagebuilder/lib/styles/cn.ts +5 -0
- package/src/templates/next-pagebuilder/lib/styles/global.css +70 -0
- package/src/templates/next-pagebuilder/lib/utils/base-url.ts +17 -0
- package/src/templates/next-pagebuilder/lib/utils/format-date.ts +8 -0
- package/src/templates/next-pagebuilder/lib/utils/json-ld.tsx +213 -0
- package/src/templates/next-pagebuilder/lib/utils/metadata.ts +167 -0
- package/src/templates/next-pagebuilder/lib/utils/sitemap.ts +37 -0
- package/src/templates/next-pagebuilder/lib/utils/slug-tag.ts +6 -0
- package/src/templates/next-pagebuilder/next.config.ts +134 -0
- package/src/templates/next-pagebuilder/package.json +71 -0
- package/src/templates/next-pagebuilder/postcss.config.mjs +39 -0
- package/src/templates/next-pagebuilder/proxy.ts +81 -0
- package/src/templates/next-pagebuilder/svg.d.ts +5 -0
- package/src/templates/next-pagebuilder/tsconfig.json +38 -0
- package/src/helpers/integrate/sanity/files/lib/scripts/copy-sanity-mcp.ts +0 -23
- package/src/helpers/integrate/sanity/files/lib/scripts/generate-page.ts +0 -297
|
@@ -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>© {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">© {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
|
+
}
|
package/src/templates/next-pagebuilder/components/layout/header/components/mega-menu-panel.tsx
ADDED
|
@@ -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
|
+
}
|
package/src/templates/next-pagebuilder/components/layout/header/components/nav-item-renderer.tsx
ADDED
|
@@ -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]
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { useEffect, useRef, useState } from "react"
|
|
4
|
+
|
|
5
|
+
import { Image } from "@/components/ui/image"
|
|
6
|
+
import { Link } from "@/components/ui/link"
|
|
7
|
+
import type { NavbarData } from "@/lib/integrations/sanity/fetchers/layout"
|
|
8
|
+
|
|
9
|
+
import { CtaButtonRenderer } from "./components/cta-button"
|
|
10
|
+
import { NavItemRenderer } from "./components/nav-item-renderer"
|
|
11
|
+
|
|
12
|
+
export const HeaderClient = ({ data: navbar }: { data: NavbarData | null }) => {
|
|
13
|
+
const [openIndex, setOpenIndex] = useState<number | null>(null)
|
|
14
|
+
const navRef = useRef<HTMLElement>(null)
|
|
15
|
+
const triggerRefs = useRef<Map<number, HTMLButtonElement>>(new Map())
|
|
16
|
+
|
|
17
|
+
const logoUrl = navbar?.logo?.asset?.url
|
|
18
|
+
const navigationItems = navbar?.navigationItems ?? []
|
|
19
|
+
const ctaButtons = navbar?.ctaButtons ?? []
|
|
20
|
+
|
|
21
|
+
// Close on click outside & Escape
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
if (openIndex === null) return
|
|
24
|
+
|
|
25
|
+
const onClick = (e: MouseEvent) => {
|
|
26
|
+
if (
|
|
27
|
+
navRef.current &&
|
|
28
|
+
e.target instanceof Node &&
|
|
29
|
+
!navRef.current.contains(e.target)
|
|
30
|
+
) {
|
|
31
|
+
setOpenIndex(null)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const onKeyDown = (e: KeyboardEvent) => {
|
|
36
|
+
if (e.key === "Escape") {
|
|
37
|
+
const prevIndex = openIndex
|
|
38
|
+
setOpenIndex(null)
|
|
39
|
+
triggerRefs.current.get(prevIndex)?.focus()
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
document.addEventListener("click", onClick)
|
|
44
|
+
document.addEventListener("keydown", onKeyDown)
|
|
45
|
+
return () => {
|
|
46
|
+
document.removeEventListener("click", onClick)
|
|
47
|
+
document.removeEventListener("keydown", onKeyDown)
|
|
48
|
+
}
|
|
49
|
+
}, [openIndex])
|
|
50
|
+
|
|
51
|
+
const handleToggle = (index: number) => {
|
|
52
|
+
setOpenIndex((prev) => (prev === index ? null : index))
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<nav
|
|
57
|
+
ref={navRef}
|
|
58
|
+
className="sticky top-0 z-50 border-black/10 border-b bg-white"
|
|
59
|
+
aria-label="Main navigation"
|
|
60
|
+
>
|
|
61
|
+
<div className="mx-auto flex max-w-7xl items-center justify-between px-6 py-4">
|
|
62
|
+
{/* Logo */}
|
|
63
|
+
<Link href="/" className="shrink-0" aria-label="Home">
|
|
64
|
+
{logoUrl ? (
|
|
65
|
+
<Image
|
|
66
|
+
src={logoUrl}
|
|
67
|
+
alt="Basement"
|
|
68
|
+
width={131}
|
|
69
|
+
height={23}
|
|
70
|
+
className="h-8 w-auto"
|
|
71
|
+
/>
|
|
72
|
+
) : (
|
|
73
|
+
<span className="font-semibold text-black text-lg">Basement</span>
|
|
74
|
+
)}
|
|
75
|
+
</Link>
|
|
76
|
+
|
|
77
|
+
{/* Navigation items */}
|
|
78
|
+
{navigationItems.length > 0 ? (
|
|
79
|
+
<ul className="hidden items-center gap-8 lg:flex">
|
|
80
|
+
{navigationItems.map((item, i) => (
|
|
81
|
+
<NavItemRenderer
|
|
82
|
+
key={item._key}
|
|
83
|
+
item={item}
|
|
84
|
+
isOpen={openIndex === i}
|
|
85
|
+
onToggle={() => handleToggle(i)}
|
|
86
|
+
triggerRef={(el) => {
|
|
87
|
+
if (el) {
|
|
88
|
+
triggerRefs.current.set(i, el)
|
|
89
|
+
return
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
triggerRefs.current.delete(i)
|
|
93
|
+
}}
|
|
94
|
+
/>
|
|
95
|
+
))}
|
|
96
|
+
</ul>
|
|
97
|
+
) : null}
|
|
98
|
+
|
|
99
|
+
{/* CTA buttons */}
|
|
100
|
+
{ctaButtons.length > 0 ? (
|
|
101
|
+
<div className="hidden items-center gap-3 lg:flex">
|
|
102
|
+
{ctaButtons.map((cta) => (
|
|
103
|
+
<CtaButtonRenderer key={cta._key} cta={cta} />
|
|
104
|
+
))}
|
|
105
|
+
</div>
|
|
106
|
+
) : null}
|
|
107
|
+
</div>
|
|
108
|
+
</nav>
|
|
109
|
+
)
|
|
110
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { stegaClean } from "next-sanity"
|
|
2
|
+
import {
|
|
3
|
+
getCompanyData,
|
|
4
|
+
getNavbar,
|
|
5
|
+
} from "@/lib/integrations/sanity/fetchers/layout"
|
|
6
|
+
import {
|
|
7
|
+
generateOrganizationJsonLd,
|
|
8
|
+
generateWebSiteJsonLd,
|
|
9
|
+
JsonLd as JsonLdBase,
|
|
10
|
+
} from "@/lib/utils/json-ld"
|
|
11
|
+
|
|
12
|
+
const APP_DESCRIPTION =
|
|
13
|
+
"Basement is the AI-native platform for audit and advisory firms. Automate engagement workflows using AI agents trusted by 50% of top 100 firms."
|
|
14
|
+
|
|
15
|
+
export const JsonLd = async () => {
|
|
16
|
+
const [navbarData, companyData] = await Promise.all([
|
|
17
|
+
getNavbar(),
|
|
18
|
+
getCompanyData(),
|
|
19
|
+
])
|
|
20
|
+
|
|
21
|
+
const sameAs = companyData?.socialLinks
|
|
22
|
+
?.map((link) => stegaClean(link.url))
|
|
23
|
+
.filter((url): url is string => Boolean(url))
|
|
24
|
+
|
|
25
|
+
const logoUrl = navbarData?.logo?.asset?.url
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<>
|
|
29
|
+
<JsonLdBase
|
|
30
|
+
data={generateWebSiteJsonLd({
|
|
31
|
+
name: "Basement",
|
|
32
|
+
description: APP_DESCRIPTION,
|
|
33
|
+
})}
|
|
34
|
+
/>
|
|
35
|
+
<JsonLdBase
|
|
36
|
+
data={generateOrganizationJsonLd({
|
|
37
|
+
name: "Basement",
|
|
38
|
+
...(logoUrl ? { logo: logoUrl } : {}),
|
|
39
|
+
description: APP_DESCRIPTION,
|
|
40
|
+
...(sameAs?.length ? { sameAs } : {}),
|
|
41
|
+
})}
|
|
42
|
+
/>
|
|
43
|
+
</>
|
|
44
|
+
)
|
|
45
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import Link from "next/link"
|
|
2
|
+
import { Footer } from "@/components/layout/footer"
|
|
3
|
+
import { Header } from "@/components/layout/header"
|
|
4
|
+
import { cn } from "@/lib/styles/cn"
|
|
5
|
+
|
|
6
|
+
interface WrapperProps extends React.HTMLAttributes<HTMLDivElement> {}
|
|
7
|
+
|
|
8
|
+
const SkipToMainContent = () => (
|
|
9
|
+
<Link
|
|
10
|
+
href="#main-content"
|
|
11
|
+
className="sr-only focus:not-sr-only focus:fixed focus:top-4 focus:left-4 focus:z-9999 focus:bg-black focus:p-2 focus:text-white focus:outline-none focus:ring-2 focus:ring-white"
|
|
12
|
+
>
|
|
13
|
+
Skip to main content
|
|
14
|
+
</Link>
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
export const Wrapper = ({ children, className, ...props }: WrapperProps) => (
|
|
18
|
+
<>
|
|
19
|
+
<SkipToMainContent />
|
|
20
|
+
<Header />
|
|
21
|
+
<main
|
|
22
|
+
id="main-content"
|
|
23
|
+
className={cn("relative flex grow flex-col", className)}
|
|
24
|
+
{...props}
|
|
25
|
+
>
|
|
26
|
+
{children}
|
|
27
|
+
</main>
|
|
28
|
+
<Footer />
|
|
29
|
+
</>
|
|
30
|
+
)
|
package/src/templates/next-pagebuilder/components/page-builder/components/article-content/index.tsx
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { RichText } from "@/components/sanity/rich-text"
|
|
2
|
+
import { SanityImage } from "@/components/ui/sanity-image"
|
|
3
|
+
import { formatDate } from "@/lib/utils/format-date"
|
|
4
|
+
import {RelatedPostItem} from "./related-post-item"
|
|
5
|
+
import type { ArticleContentProps } from "@/components/page-builder/types"
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
export function ArticleContent({
|
|
9
|
+
block
|
|
10
|
+
}: ArticleContentProps) {
|
|
11
|
+
const { author, body, categories, date, relatedPosts, thumbnail } = block
|
|
12
|
+
return (
|
|
13
|
+
<div className="py-8 md:py-12">
|
|
14
|
+
<header className="mx-auto max-w-3xl space-y-6">
|
|
15
|
+
{categories && categories.length > 0 ? (
|
|
16
|
+
<div className="flex flex-wrap gap-2">
|
|
17
|
+
{/* @ts-ignore */}
|
|
18
|
+
{categories.map((category) => (
|
|
19
|
+
<span
|
|
20
|
+
key={category._id}
|
|
21
|
+
className="rounded-full border border-current/20 px-3 py-1 text-xs"
|
|
22
|
+
>
|
|
23
|
+
{category.title}
|
|
24
|
+
</span>
|
|
25
|
+
))}
|
|
26
|
+
</div>
|
|
27
|
+
) : null}
|
|
28
|
+
|
|
29
|
+
<div className="flex items-center gap-3">
|
|
30
|
+
{author?.avatar ? (
|
|
31
|
+
<SanityImage
|
|
32
|
+
image={author.avatar}
|
|
33
|
+
alt={author.name || ""}
|
|
34
|
+
className="size-10 rounded-full object-cover"
|
|
35
|
+
/>
|
|
36
|
+
) : null}
|
|
37
|
+
<div className="flex flex-col">
|
|
38
|
+
{author?.name ? (
|
|
39
|
+
<span className="font-medium text-sm">{author.name}</span>
|
|
40
|
+
) : null}
|
|
41
|
+
<div className="flex items-center gap-2 text-current/60 text-sm">
|
|
42
|
+
{author?.role ? <span>{author.role}</span> : null}
|
|
43
|
+
{author?.role && date ? <span aria-hidden>·</span> : null}
|
|
44
|
+
{date ? <time dateTime={date}>{formatDate(date)}</time> : null}
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
</header>
|
|
49
|
+
|
|
50
|
+
{thumbnail ? (
|
|
51
|
+
<div className="mx-auto mt-8 max-w-4xl overflow-hidden rounded-lg">
|
|
52
|
+
<SanityImage
|
|
53
|
+
image={thumbnail}
|
|
54
|
+
alt={thumbnail.alt || ""}
|
|
55
|
+
priority
|
|
56
|
+
className="aspect-video w-full object-cover"
|
|
57
|
+
/>
|
|
58
|
+
</div>
|
|
59
|
+
) : null}
|
|
60
|
+
|
|
61
|
+
{body ? (
|
|
62
|
+
<div className="mx-auto mt-10 max-w-3xl space-y-4 text-base/7 text-current/80 md:text-lg/8">
|
|
63
|
+
<RichText content={body} />
|
|
64
|
+
</div>
|
|
65
|
+
) : null}
|
|
66
|
+
|
|
67
|
+
{relatedPosts && relatedPosts.length > 0 ? (
|
|
68
|
+
<aside className="mx-auto mt-16 max-w-3xl">
|
|
69
|
+
<h2 className="font-semibold text-2xl">Related Articles</h2>
|
|
70
|
+
<ul className="mt-6 grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
|
|
71
|
+
{relatedPosts.map((post) => {
|
|
72
|
+
if (!post.resolvedSlug) return null
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<RelatedPostItem key={post._id} post={post} />
|
|
76
|
+
)
|
|
77
|
+
})}
|
|
78
|
+
</ul>
|
|
79
|
+
</aside>
|
|
80
|
+
) : null}
|
|
81
|
+
</div>
|
|
82
|
+
)
|
|
83
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Link } from "@/components/ui/link"
|
|
2
|
+
import { SanityImage } from "@/components/ui/sanity-image"
|
|
3
|
+
import type { ArticleRelatedPost } from "@/components/page-builder/types"
|
|
4
|
+
|
|
5
|
+
export const RelatedPostItem = ({ post }: {post: ArticleRelatedPost}) => {
|
|
6
|
+
return (
|
|
7
|
+
<li key={post._id}>
|
|
8
|
+
<Link
|
|
9
|
+
href={`/${post.resolvedSlug}`}
|
|
10
|
+
className="group flex flex-col gap-3"
|
|
11
|
+
>
|
|
12
|
+
{post.metadata?.image ? (
|
|
13
|
+
<div className="overflow-hidden rounded-lg">
|
|
14
|
+
<SanityImage
|
|
15
|
+
image={post.metadata.image}
|
|
16
|
+
alt={post.title || ""}
|
|
17
|
+
className="aspect-[16/9] w-full object-cover transition-transform duration-300 group-hover:scale-105"
|
|
18
|
+
/>
|
|
19
|
+
</div>
|
|
20
|
+
) : null}
|
|
21
|
+
<h3 className="text-balance font-medium text-base leading-snug">
|
|
22
|
+
{post.title}
|
|
23
|
+
</h3>
|
|
24
|
+
</Link>
|
|
25
|
+
</li>
|
|
26
|
+
)
|
|
27
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { PageBuilderBlock } from "../types"
|
|
2
|
+
|
|
3
|
+
type DescriptionProps = {
|
|
4
|
+
block: Extract<PageBuilderBlock, { _type: "description" }>
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function Description({ block }: DescriptionProps) {
|
|
8
|
+
if (!block.description) return null
|
|
9
|
+
|
|
10
|
+
return (
|
|
11
|
+
<section className="py-4">
|
|
12
|
+
<p className="max-w-[62ch] text-base/7 text-current/72 md:text-lg/8">
|
|
13
|
+
{block.description}
|
|
14
|
+
</p>
|
|
15
|
+
</section>
|
|
16
|
+
)
|
|
17
|
+
}
|