bsmnt 0.12.0 → 0.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/application/add-integration/index.d.ts.map +1 -1
- package/dist/application/add-integration/index.js +23 -0
- package/dist/application/add-integration/index.js.map +1 -1
- package/dist/domain/consent.d.ts +17 -0
- package/dist/domain/consent.d.ts.map +1 -0
- package/dist/domain/consent.js +13 -0
- package/dist/domain/consent.js.map +1 -0
- package/dist/domain/integration.d.ts +19 -2
- package/dist/domain/integration.d.ts.map +1 -1
- package/dist/domain/integration.js +10 -1
- package/dist/domain/integration.js.map +1 -1
- package/dist/domain/jobs.d.ts +17 -0
- package/dist/domain/jobs.d.ts.map +1 -0
- package/dist/domain/jobs.js +13 -0
- package/dist/domain/jobs.js.map +1 -0
- package/dist/domain/skills.d.ts +23 -2
- package/dist/domain/skills.d.ts.map +1 -1
- package/dist/domain/skills.js +23 -2
- package/dist/domain/skills.js.map +1 -1
- package/dist/domain/templates.d.ts +7 -1
- package/dist/domain/templates.d.ts.map +1 -1
- package/dist/domain/templates.js +2 -0
- package/dist/domain/templates.js.map +1 -1
- package/dist/index.js +34 -9
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/create/setup-agent.d.ts.map +1 -1
- package/dist/infrastructure/create/setup-agent.js +11 -4
- package/dist/infrastructure/create/setup-agent.js.map +1 -1
- package/dist/infrastructure/integrations/inject-integration.d.ts.map +1 -1
- package/dist/infrastructure/integrations/inject-integration.js +36 -2
- package/dist/infrastructure/integrations/inject-integration.js.map +1 -1
- package/dist/modules/features/consent/ketch/config.d.ts +18 -0
- package/dist/modules/features/consent/ketch/config.d.ts.map +1 -0
- package/dist/modules/features/consent/ketch/config.js +24 -0
- package/dist/modules/features/consent/ketch/config.js.map +1 -0
- package/dist/modules/features/consent/ketch/mergers/biome.d.ts +16 -0
- package/dist/modules/features/consent/ketch/mergers/biome.d.ts.map +1 -0
- package/dist/modules/features/consent/ketch/mergers/biome.js +31 -0
- package/dist/modules/features/consent/ketch/mergers/biome.js.map +1 -0
- package/dist/modules/features/consent/ketch/mergers/check-integration.d.ts +13 -0
- package/dist/modules/features/consent/ketch/mergers/check-integration.d.ts.map +1 -0
- package/dist/modules/features/consent/ketch/mergers/check-integration.js +64 -0
- package/dist/modules/features/consent/ketch/mergers/check-integration.js.map +1 -0
- package/dist/modules/features/consent/ketch/mergers/env-example.d.ts +12 -0
- package/dist/modules/features/consent/ketch/mergers/env-example.d.ts.map +1 -0
- package/dist/modules/features/consent/ketch/mergers/env-example.js +28 -0
- package/dist/modules/features/consent/ketch/mergers/env-example.js.map +1 -0
- package/dist/modules/features/consent/ketch/mergers/footer.d.ts +17 -0
- package/dist/modules/features/consent/ketch/mergers/footer.d.ts.map +1 -0
- package/dist/modules/features/consent/ketch/mergers/footer.js +40 -0
- package/dist/modules/features/consent/ketch/mergers/footer.js.map +1 -0
- package/dist/modules/features/consent/ketch/mergers/knip.d.ts +18 -0
- package/dist/modules/features/consent/ketch/mergers/knip.d.ts.map +1 -0
- package/dist/modules/features/consent/ketch/mergers/knip.js +37 -0
- package/dist/modules/features/consent/ketch/mergers/knip.js.map +1 -0
- package/dist/modules/features/consent/ketch/mergers/layout.d.ts +25 -0
- package/dist/modules/features/consent/ketch/mergers/layout.d.ts.map +1 -0
- package/dist/modules/features/consent/ketch/mergers/layout.js +55 -0
- package/dist/modules/features/consent/ketch/mergers/layout.js.map +1 -0
- package/dist/modules/features/jobs/ashby/config.d.ts +19 -0
- package/dist/modules/features/jobs/ashby/config.d.ts.map +1 -0
- package/dist/modules/features/jobs/ashby/config.js +29 -0
- package/dist/modules/features/jobs/ashby/config.js.map +1 -0
- package/dist/modules/features/jobs/ashby/mergers/check-integration.d.ts +14 -0
- package/dist/modules/features/jobs/ashby/mergers/check-integration.d.ts.map +1 -0
- package/dist/modules/features/jobs/ashby/mergers/check-integration.js +78 -0
- package/dist/modules/features/jobs/ashby/mergers/check-integration.js.map +1 -0
- package/dist/modules/features/jobs/ashby/mergers/env-example.d.ts +12 -0
- package/dist/modules/features/jobs/ashby/mergers/env-example.d.ts.map +1 -0
- package/dist/modules/features/jobs/ashby/mergers/env-example.js +27 -0
- package/dist/modules/features/jobs/ashby/mergers/env-example.js.map +1 -0
- package/dist/modules/features/jobs/ashby/mergers/knip.d.ts +18 -0
- package/dist/modules/features/jobs/ashby/mergers/knip.d.ts.map +1 -0
- package/dist/modules/features/jobs/ashby/mergers/knip.js +41 -0
- package/dist/modules/features/jobs/ashby/mergers/knip.js.map +1 -0
- package/dist/paths.d.ts +12 -0
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +12 -0
- package/dist/paths.js.map +1 -1
- package/package.json +3 -1
- package/src/agent-skills/ashby-pagebuilder-block/SKILL.md +277 -0
- package/src/agent-skills/hubspot-pagebuilder-block/SKILL.md +58 -25
- package/src/agent-skills/ketch-consent-check/SKILL.md +197 -0
- package/src/agent-skills/ketch-consent-setup/SKILL.md +326 -0
- package/src/agent-skills/ketch-consent-setup/references/ui-customization.md +329 -0
- package/src/agent-skills/page-builder-block/SKILL.md +150 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/anchor-id-input.tsx +17 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/presentation-input.tsx +146 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/spacing-steps-input.tsx +84 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/fetchers/layout.ts +6 -3
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/icons.ts +8 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/page-builder-config.ts +1 -1
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/presentation.ts +47 -15
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/queries.ts +12 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/reusable/blog-content.ts +5 -5
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/reusable/description.ts +3 -3
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/reusable/gated-content.ts +3 -3
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/reusable/hero.ts +3 -3
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/reusable/rich-text-block.ts +3 -3
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/singleton/content-collection.ts +3 -3
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/index.ts +8 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/layout/spacing-settings.ts +120 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/anchor.ts +75 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/block.ts +46 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/presentation.ts +44 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/spacing.ts +164 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/singletons.ts +7 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/structure.ts +4 -4
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/anchor-id-input.tsx +17 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/presentation-input.tsx +146 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/spacing-steps-input.tsx +84 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/fetchers/layout.ts +4 -2
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/icons.ts +8 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/page-builder-config.ts +1 -1
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/presentation.ts +47 -15
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/queries.ts +12 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/reusable/blog-content.ts +5 -5
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/reusable/description.ts +3 -3
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/reusable/gated-content.ts +3 -3
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/reusable/hero.ts +3 -3
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/reusable/rich-text-block.ts +3 -3
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/singleton/content-collection.ts +3 -3
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/index.ts +8 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/layout/spacing-settings.ts +120 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/anchor.ts +75 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/block.ts +46 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/presentation.ts +44 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/spacing.ts +164 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/singletons.ts +7 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/structure.ts +4 -4
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/tags.ts +2 -0
- package/src/modules/features/consent/ketch/files/lib/integrations/check-integration.ts +25 -0
- package/src/modules/features/consent/ketch/files/lib/integrations/ketch/README.md +70 -0
- package/src/modules/features/consent/ketch/files/lib/integrations/ketch/categories.ts +20 -0
- package/src/modules/features/consent/ketch/files/lib/integrations/ketch/consent-store.ts +107 -0
- package/src/modules/features/consent/ketch/files/lib/integrations/ketch/env.ts +20 -0
- package/src/modules/features/consent/ketch/files/lib/integrations/ketch/ketch-experience.css +136 -0
- package/src/modules/features/consent/ketch/files/lib/integrations/ketch/preferences-button.tsx +51 -0
- package/src/modules/features/consent/ketch/files/lib/integrations/ketch/script.tsx +8 -0
- package/src/modules/features/consent/ketch/files/lib/integrations/ketch/types.ts +15 -0
- package/src/modules/features/consent/ketch/files/lib/integrations/ketch/use-consent.ts +26 -0
- package/src/modules/features/jobs/ashby/files/_sanity-adapter/job-board-block.ts +53 -0
- package/src/modules/features/jobs/ashby/files/lib/integrations/ashby/README.md +134 -0
- package/src/modules/features/jobs/ashby/files/lib/integrations/ashby/env.ts +3 -0
- package/src/modules/features/jobs/ashby/files/lib/integrations/ashby/index.ts +40 -0
- package/src/modules/features/jobs/ashby/files/lib/integrations/ashby/normalize.ts +124 -0
- package/src/modules/features/jobs/ashby/files/lib/integrations/ashby/types.ts +20 -0
- package/src/modules/features/jobs/ashby/files/lib/integrations/check-integration.ts +25 -0
- package/src/templates/next-default/lib/utils/README.md +1 -0
- package/src/templates/next-default/lib/utils/image-sizes.ts +64 -25
- package/src/templates/next-experiments/lib/utils/README.md +1 -0
- package/src/templates/next-experiments/lib/utils/image-sizes.ts +64 -25
- package/src/templates/next-pagebuilder/AGENTS.md +129 -0
- package/src/templates/next-pagebuilder/CLAUDE.md +3 -0
- package/src/templates/next-pagebuilder/README.md +70 -0
- package/src/templates/next-pagebuilder/app/(content)/layout.tsx +2 -0
- package/src/templates/next-pagebuilder/components/layout/spacing-scale/index.tsx +18 -0
- package/src/templates/next-pagebuilder/components/page-builder/components/article-content/index.tsx +1 -1
- package/src/templates/next-pagebuilder/components/page-builder/components/content-collection/index.tsx +2 -2
- package/src/templates/next-pagebuilder/components/page-builder/components/description.tsx +3 -7
- package/src/templates/next-pagebuilder/components/page-builder/components/gated-content/index.tsx +2 -2
- package/src/templates/next-pagebuilder/components/page-builder/components/hero.tsx +3 -7
- package/src/templates/next-pagebuilder/components/page-builder/components/rich-text-block.tsx +3 -5
- package/src/templates/next-pagebuilder/components/page-builder/registry.tsx +70 -0
- package/src/templates/next-pagebuilder/components/page-builder/renderer.tsx +33 -49
- package/src/templates/next-pagebuilder/components/page-builder/section.tsx +35 -0
- package/src/templates/next-pagebuilder/components/page-document/index.tsx +2 -2
- package/src/templates/next-pagebuilder/lib/integrations/sanity/sanity.types.ts +92 -4
- package/src/templates/next-pagebuilder/lib/utils/image-sizes.ts +64 -25
- package/src/templates/next-pagebuilder-cache/AGENTS.md +136 -0
- package/src/templates/next-pagebuilder-cache/CLAUDE.md +3 -0
- package/src/templates/next-pagebuilder-cache/README.md +75 -0
- package/src/templates/next-pagebuilder-cache/app/(content)/layout.tsx +2 -0
- package/src/templates/next-pagebuilder-cache/components/layout/spacing-scale/index.tsx +46 -0
- package/src/templates/next-pagebuilder-cache/components/page-builder/components/article-content/index.tsx +1 -1
- package/src/templates/next-pagebuilder-cache/components/page-builder/components/content-collection/index.tsx +2 -2
- package/src/templates/next-pagebuilder-cache/components/page-builder/components/description.tsx +3 -7
- package/src/templates/next-pagebuilder-cache/components/page-builder/components/gated-content/index.tsx +2 -2
- package/src/templates/next-pagebuilder-cache/components/page-builder/components/hero.tsx +3 -7
- package/src/templates/next-pagebuilder-cache/components/page-builder/components/rich-text-block.tsx +3 -5
- package/src/templates/next-pagebuilder-cache/components/page-builder/registry.tsx +70 -0
- package/src/templates/next-pagebuilder-cache/components/page-builder/renderer.tsx +33 -49
- package/src/templates/next-pagebuilder-cache/components/page-builder/section.tsx +35 -0
- package/src/templates/next-pagebuilder-cache/components/page-document/index.tsx +2 -2
- package/src/templates/next-pagebuilder-cache/lib/integrations/sanity/sanity.types.ts +92 -4
- package/src/templates/next-pagebuilder-cache/lib/utils/image-sizes.ts +64 -25
- package/src/templates/next-webgl/lib/utils/README.md +1 -0
- package/src/templates/next-webgl/lib/utils/image-sizes.ts +64 -25
|
@@ -15,40 +15,55 @@ export type ByBreakpoint<
|
|
|
15
15
|
B extends BreakpointMap = typeof BASEMENT_BREAKPOINTS,
|
|
16
16
|
> = { base: T } & Partial<Record<Extract<keyof B, string>, T>>
|
|
17
17
|
|
|
18
|
+
export type ImageSizesOptions<
|
|
19
|
+
B extends BreakpointMap = typeof BASEMENT_BREAKPOINTS,
|
|
20
|
+
> = { breakpoints?: B }
|
|
21
|
+
|
|
22
|
+
export type GridImageSizesOptions<
|
|
23
|
+
B extends BreakpointMap = typeof BASEMENT_BREAKPOINTS,
|
|
24
|
+
> = ImageSizesOptions<B> & {
|
|
25
|
+
/** The grid container's `max-width` in px, past which columns stop growing. */
|
|
26
|
+
maxWidth?: number
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
type Condition<T> = { min: number; value: T }
|
|
30
|
+
|
|
18
31
|
function columnWidth(columns: number): string {
|
|
19
32
|
return `${Math.round(100 / columns)}vw`
|
|
20
33
|
}
|
|
21
34
|
|
|
35
|
+
function toConditions<T>(
|
|
36
|
+
overrides: Readonly<Record<string, T | undefined>>,
|
|
37
|
+
scale: BreakpointMap
|
|
38
|
+
): Condition<T>[] {
|
|
39
|
+
return Object.entries(overrides).flatMap(([name, value]) => {
|
|
40
|
+
const min = scale[name]
|
|
41
|
+
return value !== undefined && min !== undefined ? [{ min, value }] : []
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
|
|
22
45
|
/**
|
|
23
46
|
* Widest first, since `sizes` takes the first matching condition and the last
|
|
24
47
|
* entry must be bare. `min-width` matches the Tailwind variant's boundary; a
|
|
25
48
|
* `max-width: <next> - 1` one would leave a fractional-pixel gap.
|
|
26
49
|
*/
|
|
27
|
-
function
|
|
28
|
-
|
|
29
|
-
overrides: Readonly<Record<string, string | undefined>>,
|
|
30
|
-
scale: BreakpointMap
|
|
31
|
-
): string {
|
|
32
|
-
const conditions: string[] = []
|
|
33
|
-
|
|
34
|
-
for (const [name, min] of Object.entries(scale).sort(
|
|
35
|
-
([, a], [, b]) => b - a
|
|
36
|
-
)) {
|
|
37
|
-
const value = overrides[name]
|
|
38
|
-
if (value !== undefined) {
|
|
39
|
-
conditions.push(`(min-width: ${min}px) ${value}`)
|
|
40
|
-
}
|
|
41
|
-
}
|
|
50
|
+
function joinSizes(base: string, conditions: Condition<string>[]): string {
|
|
51
|
+
const sorted = [...conditions].sort((a, b) => b.min - a.min)
|
|
42
52
|
|
|
43
|
-
return [
|
|
53
|
+
return [
|
|
54
|
+
...sorted.map(({ min, value }) => `(min-width: ${min}px) ${value}`),
|
|
55
|
+
base,
|
|
56
|
+
].join(", ")
|
|
44
57
|
}
|
|
45
58
|
|
|
46
59
|
/** Builds `sizes` from the CSS length the image renders at per breakpoint. */
|
|
47
60
|
export function buildImageSizes<
|
|
48
61
|
B extends BreakpointMap = typeof BASEMENT_BREAKPOINTS,
|
|
49
|
-
>(widths: ByBreakpoint<string, B>,
|
|
62
|
+
>(widths: ByBreakpoint<string, B>, options: ImageSizesOptions<B> = {}): string {
|
|
50
63
|
const { base, ...overrides } = widths
|
|
51
|
-
|
|
64
|
+
const scale = options.breakpoints ?? BASEMENT_BREAKPOINTS
|
|
65
|
+
|
|
66
|
+
return joinSizes(base, toConditions<string>(overrides, scale))
|
|
52
67
|
}
|
|
53
68
|
|
|
54
69
|
/**
|
|
@@ -58,15 +73,39 @@ export function buildImageSizes<
|
|
|
58
73
|
*/
|
|
59
74
|
export function buildGridImageSizes<
|
|
60
75
|
B extends BreakpointMap = typeof BASEMENT_BREAKPOINTS,
|
|
61
|
-
>(
|
|
76
|
+
>(
|
|
77
|
+
columns: ByBreakpoint<number, B>,
|
|
78
|
+
options: GridImageSizesOptions<B> = {}
|
|
79
|
+
): string {
|
|
80
|
+
const { maxWidth, breakpoints } = options
|
|
62
81
|
const { base, ...overrides } = columns
|
|
63
|
-
const
|
|
82
|
+
const steps = toConditions<number>(
|
|
83
|
+
overrides,
|
|
84
|
+
breakpoints ?? BASEMENT_BREAKPOINTS
|
|
85
|
+
)
|
|
86
|
+
const cap =
|
|
87
|
+
maxWidth !== undefined && Number.isFinite(maxWidth) && maxWidth > 0
|
|
88
|
+
? maxWidth
|
|
89
|
+
: undefined
|
|
90
|
+
|
|
91
|
+
// The cap rarely lands on a breakpoint, so it needs its own condition.
|
|
92
|
+
if (cap !== undefined && !steps.some(({ min }) => min === cap)) {
|
|
93
|
+
const below = steps
|
|
94
|
+
.filter(({ min }) => min < cap)
|
|
95
|
+
.sort((a, b) => a.min - b.min)
|
|
96
|
+
.at(-1)
|
|
64
97
|
|
|
65
|
-
|
|
66
|
-
if (count !== undefined) {
|
|
67
|
-
widths[name] = columnWidth(count)
|
|
68
|
-
}
|
|
98
|
+
steps.push({ min: cap, value: below?.value ?? base })
|
|
69
99
|
}
|
|
70
100
|
|
|
71
|
-
return
|
|
101
|
+
return joinSizes(
|
|
102
|
+
columnWidth(base),
|
|
103
|
+
steps.map(({ min, value }) => ({
|
|
104
|
+
min,
|
|
105
|
+
value:
|
|
106
|
+
cap !== undefined && min >= cap
|
|
107
|
+
? `${Math.round(cap / value)}px`
|
|
108
|
+
: columnWidth(value),
|
|
109
|
+
}))
|
|
110
|
+
)
|
|
72
111
|
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
Instructions for AI agents working in this repository. Human-facing setup,
|
|
4
|
+
environment variables and the publish webhook live in the README — read it too.
|
|
5
|
+
|
|
6
|
+
This is a **Next.js + Sanity page builder**: all URL structure is managed in
|
|
7
|
+
Sanity, not in code, and a single catch-all route `[[...slug]]` renders every
|
|
8
|
+
page.
|
|
9
|
+
|
|
10
|
+
**Never add a page route under `app/`.** A new page is a `page` document in the
|
|
11
|
+
Studio with a slug, not a directory. `app/(content)/[[...slug]]/page.tsx`
|
|
12
|
+
resolves every one of them; a hand-written route shadows that resolver and
|
|
13
|
+
becomes a URL no editor can see, move or delete. The only things that belong in
|
|
14
|
+
`app/` are non-page endpoints — the Studio, API handlers, sitemap, feed.
|
|
15
|
+
|
|
16
|
+
It runs with [Cache Components](https://nextjs.org/docs/app/api-reference/config/next-config-js/cacheComponents)
|
|
17
|
+
(`cacheComponents: true`), so every Sanity read sits behind a `'use cache'`
|
|
18
|
+
boundary and lands in the static shell. Nothing is time-based — the publish
|
|
19
|
+
webhook is what invalidates. A new read that isn't cached or wrapped in
|
|
20
|
+
`<Suspense>` takes its whole route out of the static shell, so keep the
|
|
21
|
+
boundaries where they are unless you mean to move them.
|
|
22
|
+
|
|
23
|
+
## Page-builder blocks
|
|
24
|
+
|
|
25
|
+
**A block is `defineBlock` + a registry entry.** Define the schema with
|
|
26
|
+
`defineBlock` (`lib/integrations/sanity/schemas/shared/block.ts`), never
|
|
27
|
+
`defineType` — it implies `type: "document"` and appends **Anchor ID** and
|
|
28
|
+
**Presentation Mode** for you. Register the component in
|
|
29
|
+
`components/page-builder/registry.tsx`; the renderer is driven by that map, so
|
|
30
|
+
there is no switch to extend.
|
|
31
|
+
|
|
32
|
+
Both halves are enforced, not documented: the registry's key set is typed as
|
|
33
|
+
exactly `PageBuilderDocumentType`, so a block registered in
|
|
34
|
+
`page-builder-config.ts` without an entry there — or an entry there for a block
|
|
35
|
+
that was never registered — is a typecheck error.
|
|
36
|
+
|
|
37
|
+
**Every block declares two orthogonal axes** in `page-builder-config.ts`:
|
|
38
|
+
`pageTypes` (which page documents may host it — drives validation) and
|
|
39
|
+
`category` (what the block structurally is, one of `componentCategories` —
|
|
40
|
+
drives the insert menu; `misc` only when nothing else fits). Never collapse them
|
|
41
|
+
into one field: that is what made the menu a single flat list.
|
|
42
|
+
|
|
43
|
+
**And a preview image.** The insert menu renders one tile per block from a
|
|
44
|
+
`studioPreview` document (Components → Component Previews) keyed
|
|
45
|
+
`block.<memberName>`. A block without one falls back to its schema icon, which
|
|
46
|
+
is what made the old menu unreadable.
|
|
47
|
+
|
|
48
|
+
**Never hardcode vertical padding in a block component.** The renderer wraps
|
|
49
|
+
each block in `<Section>` (`components/page-builder/section.tsx`), which owns
|
|
50
|
+
the `<section>` element, the anchor `id`, `scroll-mt` and the responsive padding
|
|
51
|
+
top/bottom. A block component returns its content only — no outer `<section>`,
|
|
52
|
+
no `py-*` / `pt-*` / `pb-*` on its root. Internal gaps between a block's own
|
|
53
|
+
parts are fine.
|
|
54
|
+
|
|
55
|
+
**Never hardcode px for section spacing either.** The scale lives in the Studio
|
|
56
|
+
at **Layout → Spacing Scale**, where editors add or rename steps and mark one as
|
|
57
|
+
the default, and it reaches the page as CSS custom properties. `Section` only
|
|
58
|
+
references `var(--section-space-<key>, var(--section-space-default, 2.5rem))`.
|
|
59
|
+
|
|
60
|
+
**An empty Anchor ID is generated from the block's title** (`headline` /
|
|
61
|
+
`heading` / `teaserHeading` / `title` / `label` — never `internalTitle`, which is
|
|
62
|
+
Studio-only) and de-duplicated per page. Name your block's headline field one of
|
|
63
|
+
those and anchor links work for free.
|
|
64
|
+
|
|
65
|
+
Declare `isEmpty` in the registry when a block can be left unfilled — the
|
|
66
|
+
renderer then skips the block *and* its `<Section>`, so it leaves no padded gap
|
|
67
|
+
and claims no anchor id. Don't early-return `null` from the component instead;
|
|
68
|
+
the renderer has to know before it wraps anything.
|
|
69
|
+
|
|
70
|
+
Full recipe and a checklist for a new block: the `page-builder-block` skill, or
|
|
71
|
+
the "Page-builder blocks" section of the README.
|
|
72
|
+
|
|
73
|
+
## Sanity
|
|
74
|
+
|
|
75
|
+
- Everything Sanity lives in `lib/integrations/sanity/`.
|
|
76
|
+
- Schemas use `defineType` / `defineField` / `defineArrayMember` (blocks use
|
|
77
|
+
`defineBlock`). GROQ queries are `SCREAMING_SNAKE_CASE` and wrapped in
|
|
78
|
+
`defineQuery`.
|
|
79
|
+
- **Regenerate types after any schema or query change:** `bun sanity:typegen`,
|
|
80
|
+
and commit the result in the same PR. Never hand-edit `sanity.types.ts`.
|
|
81
|
+
- The page query spreads `...` for non-gated blocks, so new block fields flow
|
|
82
|
+
through on their own — except `gatedContent`, whose GROQ branch lists fields
|
|
83
|
+
explicitly in `lib/integrations/sanity/queries.ts`. Add fields there by hand.
|
|
84
|
+
- Read through `sanityFetch` / `fetchSanity`, not a bare client, so the
|
|
85
|
+
draft-mode perspective and cache tags are applied.
|
|
86
|
+
- Publishing a document must invalidate what renders it. Layout singletons are
|
|
87
|
+
listed in `SANITY_LAYOUT_TAGS`; a new one that isn't there serves stale.
|
|
88
|
+
- Add `data-sanity` attributes where visual editing should reach.
|
|
89
|
+
- Nested layouts (e.g. `app/studio/layout.tsx`) must NOT render `<html>` or
|
|
90
|
+
`<body>` — the root layout already provides them.
|
|
91
|
+
|
|
92
|
+
## React and Next.js
|
|
93
|
+
|
|
94
|
+
- **React Compiler is on.** Do not write `useMemo`, `useCallback` or
|
|
95
|
+
`React.memo`. The one exception is `useRef` to instantiate an object once:
|
|
96
|
+
|
|
97
|
+
```tsx
|
|
98
|
+
const instanceRef = useRef<SomeClass | null>(null)
|
|
99
|
+
if (!instanceRef.current) instanceRef.current = new SomeClass()
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
- React 19: refs are regular props, so no `forwardRef` (a lint rule enforces it).
|
|
103
|
+
- **Never call `headers()`, `cookies()`, `draftMode()` or read `searchParams` in
|
|
104
|
+
a root layout** — it takes the whole page out of the prerender. Detect the
|
|
105
|
+
route with `useSelectedLayoutSegment()` in a `"use client"` wrapper, and put
|
|
106
|
+
draft-mode / visual-editing behaviour in a child component with its own
|
|
107
|
+
`<Suspense>` boundary.
|
|
108
|
+
- Use `@/components/ui/image` rather than `next/image` directly, and
|
|
109
|
+
`@/components/ui/sanity-image` for Sanity assets.
|
|
110
|
+
- Compose classNames with `cn` from `@/lib/styles/cn`.
|
|
111
|
+
- Import with the `@/` alias; relative parent imports are a lint error.
|
|
112
|
+
- Gate debug UI behind `process.env.NODE_ENV === "development"` — it is not
|
|
113
|
+
stripped in production otherwise.
|
|
114
|
+
|
|
115
|
+
## House rules
|
|
116
|
+
|
|
117
|
+
- **Bun only.** `bun install`, `bun run <script>`, `bunx`. Never npm, pnpm or
|
|
118
|
+
yarn — a second lockfile next to `bun.lock` is how the versions drift.
|
|
119
|
+
- **Don't add a dependency without asking.** Check `bun info <package>` and the
|
|
120
|
+
current docs first.
|
|
121
|
+
<!-- bsmnt:if dotenvx -->
|
|
122
|
+
- **Secrets are encrypted at rest with dotenvx.** Never put a real secret in
|
|
123
|
+
plaintext in a committed env file — gitleaks blocks the commit. Use
|
|
124
|
+
`bun env:set KEY value` (`--plain` for trivial config and `NEXT_PUBLIC_*`).
|
|
125
|
+
The `manage-env` skill has the full procedure.
|
|
126
|
+
<!-- bsmnt:endif -->
|
|
127
|
+
- Don't leave commented-out code or `TODO`s behind; write the code so it reads
|
|
128
|
+
without them.
|
|
129
|
+
- **Verify before claiming done:**
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
bun sanity:typegen && bun typecheck && bun lint && bun run build && bun knip
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
(`bun run build`, not `bun build` — the bare form is Bun's bundler.) Say what
|
|
136
|
+
you ran and what it printed; don't report success you haven't seen.
|
|
@@ -19,9 +19,84 @@ bun dev
|
|
|
19
19
|
|
|
20
20
|
- Single catch-all route `[[...slug]]` handles all pages
|
|
21
21
|
- Sanity page builder with composable content blocks
|
|
22
|
+
- Editor-owned vertical spacing scale and per-block anchor links
|
|
22
23
|
- Blog with categories, filtering, and pagination
|
|
23
24
|
- Visual editing and live preview support
|
|
24
25
|
|
|
26
|
+
## Page-builder blocks
|
|
27
|
+
|
|
28
|
+
A block is two halves — a schema document and a React component — and each half
|
|
29
|
+
has one place that makes it a block rather than an ordinary document or a loose
|
|
30
|
+
component. Use them and Anchor ID, Presentation Mode and the spacing scale come
|
|
31
|
+
for free.
|
|
32
|
+
|
|
33
|
+
**Define the schema with `defineBlock`, never `defineType`**
|
|
34
|
+
(`lib/integrations/sanity/schemas/shared/block.ts`). It implies
|
|
35
|
+
`type: "document"` and appends two fields to every block:
|
|
36
|
+
|
|
37
|
+
- **Anchor ID** — optional. Left empty, the anchor is generated from the block's
|
|
38
|
+
own title at render time (`headline` / `heading` / `teaserHeading` / `title` /
|
|
39
|
+
`label` — never `internalTitle`, which is Studio-only) and de-duplicated per
|
|
40
|
+
page, so two blocks with the same heading get `-2`. A value typed by hand
|
|
41
|
+
always wins, and the Studio shows the generated one as the placeholder. Link
|
|
42
|
+
to it as `/<page>#<anchor>`.
|
|
43
|
+
- **Presentation Mode** — padding top and bottom, each picked from the shared
|
|
44
|
+
spacing scale. No free-form px. A block that never touches it follows whichever
|
|
45
|
+
step is marked as the default.
|
|
46
|
+
|
|
47
|
+
Blocks that declare `groups` get a **Presentation** tab for both fields
|
|
48
|
+
automatically — don't list it in `groups` yourself.
|
|
49
|
+
|
|
50
|
+
**Register the component in `components/page-builder/registry.tsx`.** One entry
|
|
51
|
+
per block, keyed by schema type; the renderer is driven by that map, so there is
|
|
52
|
+
no switch to extend. `isEmpty` declares when a block has nothing to show — the
|
|
53
|
+
renderer then skips the block *and* its `<Section>`, so an unfilled block leaves
|
|
54
|
+
no padded gap behind and claims no anchor id. The registry's key set is typed as
|
|
55
|
+
exactly `PageBuilderDocumentType`, so a block registered in one place and not the
|
|
56
|
+
other is a typecheck error.
|
|
57
|
+
|
|
58
|
+
**Never hardcode vertical padding in a block component.** The renderer wraps each
|
|
59
|
+
block in `<Section>` (`components/page-builder/section.tsx`), which owns the
|
|
60
|
+
`<section>` element, the anchor `id`, `scroll-mt` and the padding. A block
|
|
61
|
+
component returns its content only — no outer `<section>`, no `py-*` / `pt-*` /
|
|
62
|
+
`pb-*` on its root. Internal gaps between a block's own parts are fine.
|
|
63
|
+
|
|
64
|
+
### The spacing scale is content, not code
|
|
65
|
+
|
|
66
|
+
Editors own it at **Layout → Spacing Scale**: a list of steps, each with a name
|
|
67
|
+
and px per breakpoint (mobile / tablet / desktop, empty reuses Desktop), one
|
|
68
|
+
marked as the default. Ticking a second step as default unticks the previous, so
|
|
69
|
+
there is always exactly one. Add a "Hero 120px" step and it shows up in every
|
|
70
|
+
block's dropdown, no deploy.
|
|
71
|
+
|
|
72
|
+
- Blocks store the step's `_key`, so renaming a step breaks nothing and a deleted
|
|
73
|
+
step degrades to the default instead of breaking the page.
|
|
74
|
+
- The list arrives seeded with the built-in five (XS 16 · SM 24 · **MD 32,
|
|
75
|
+
default** · LG 48 · XL 80, from `schemas/shared/spacing.ts`), which also apply
|
|
76
|
+
as the fallback if you empty it. Hit **"Add the built-in steps"** in the field
|
|
77
|
+
to get them back as editable rows.
|
|
78
|
+
- Values reach the page as CSS custom properties injected server-side by
|
|
79
|
+
`components/layout/spacing-scale/`, one `--section-space-<key>` per step plus
|
|
80
|
+
`--section-space-default`, re-declared inside the `768px` and `1440px` media
|
|
81
|
+
queries. `Section` only ever references
|
|
82
|
+
`var(--section-space-<key>, var(--section-space-default, 2.5rem))`, so an
|
|
83
|
+
unknown key degrades instead of breaking.
|
|
84
|
+
|
|
85
|
+
Under Cache Components the scale is read inside a `'use cache'` boundary and
|
|
86
|
+
prerenders into the static shell alongside the rest of the chrome, so the first
|
|
87
|
+
paint already carries the variables; draft mode streams the editor's copy behind
|
|
88
|
+
a `<Suspense>` that falls back to the built-in steps.
|
|
89
|
+
- `spacingSettings` is a layout singleton, so publishing it busts the layout
|
|
90
|
+
cache the same way navbar and footer do.
|
|
91
|
+
|
|
92
|
+
After a schema change run `bun sanity:typegen`. The page query spreads `...` for
|
|
93
|
+
non-gated blocks, so new fields flow through on their own — except
|
|
94
|
+
`gatedContent`, whose GROQ branch lists fields explicitly in
|
|
95
|
+
`lib/integrations/sanity/queries.ts`.
|
|
96
|
+
|
|
97
|
+
The bundled `page-builder-block` agent skill has the full recipe and a checklist
|
|
98
|
+
for adding a new block.
|
|
99
|
+
|
|
25
100
|
## Commands
|
|
26
101
|
|
|
27
102
|
- `bun dev` - Start dev server
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { draftMode } from "next/headers"
|
|
2
2
|
import { JsonLd } from "@/components/layout/json-ld"
|
|
3
|
+
import { SpacingScale } from "@/components/layout/spacing-scale"
|
|
3
4
|
import { Wrapper } from "@/components/layout/wrapper"
|
|
4
5
|
import { SanityVisualEditing } from "@/components/sanity/visual-editing"
|
|
5
6
|
import { SanityLive } from "@/lib/integrations/sanity/live"
|
|
@@ -11,6 +12,7 @@ const Layout = async ({ children }: { children: React.ReactNode }) => {
|
|
|
11
12
|
|
|
12
13
|
return (
|
|
13
14
|
<>
|
|
15
|
+
<SpacingScale isDraftMode={isDraftMode} />
|
|
14
16
|
<JsonLd isDraftMode={isDraftMode} />
|
|
15
17
|
<Wrapper isDraftMode={isDraftMode}>{children}</Wrapper>
|
|
16
18
|
{/* Draft mode only: for the public this costs a live connection and a
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Suspense } from "react"
|
|
2
|
+
import { getLayoutData } from "@/lib/integrations/sanity/fetchers/layout"
|
|
3
|
+
import {
|
|
4
|
+
type DynamicFetchOptions,
|
|
5
|
+
getDynamicFetchOptions,
|
|
6
|
+
PUBLISHED_FETCH_OPTIONS,
|
|
7
|
+
} from "@/lib/integrations/sanity/live"
|
|
8
|
+
import {
|
|
9
|
+
defaultSpacingSteps,
|
|
10
|
+
resolveSpacingSteps,
|
|
11
|
+
type SpacingStep,
|
|
12
|
+
spacingScaleCss,
|
|
13
|
+
} from "@/lib/integrations/sanity/schemas/shared/spacing"
|
|
14
|
+
|
|
15
|
+
const SpacingScaleStyle = ({ steps }: { steps: SpacingStep[] }) => (
|
|
16
|
+
<style
|
|
17
|
+
// biome-ignore lint/security/noDangerouslySetInnerHtml: css custom properties from the spacing singleton
|
|
18
|
+
dangerouslySetInnerHTML={{ __html: spacingScaleCss(steps) }}
|
|
19
|
+
/>
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
// Cached into the static shell so the first paint already has the scale; the
|
|
23
|
+
// built-in steps stand in while draft mode streams the editor's own.
|
|
24
|
+
export const SpacingScale = ({ isDraftMode }: { isDraftMode: boolean }) =>
|
|
25
|
+
isDraftMode ? (
|
|
26
|
+
<Suspense fallback={<SpacingScaleStyle steps={defaultSpacingSteps} />}>
|
|
27
|
+
<DynamicSpacingScale />
|
|
28
|
+
</Suspense>
|
|
29
|
+
) : (
|
|
30
|
+
<CachedSpacingScale {...PUBLISHED_FETCH_OPTIONS} />
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
async function DynamicSpacingScale() {
|
|
34
|
+
const fetchOptions = await getDynamicFetchOptions()
|
|
35
|
+
return <CachedSpacingScale {...fetchOptions} />
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function CachedSpacingScale(fetchOptions: DynamicFetchOptions) {
|
|
39
|
+
"use cache"
|
|
40
|
+
// One entry, shared with the header, footer and json-ld.
|
|
41
|
+
const { spacingSettings } = await getLayoutData(fetchOptions)
|
|
42
|
+
|
|
43
|
+
// Don't cap this branch: it renders in every route's shell and a nested
|
|
44
|
+
// lifetime propagates as a min(). SELF_TAGGED_TYPES clears it on publish.
|
|
45
|
+
return <SpacingScaleStyle steps={resolveSpacingSteps(spacingSettings?.steps)} />
|
|
46
|
+
}
|
|
@@ -7,7 +7,7 @@ import { RelatedPostItem } from "./related-post-item"
|
|
|
7
7
|
export function ArticleContent({ block }: ArticleContentProps) {
|
|
8
8
|
const { author, body, categories, date, relatedPosts, thumbnail } = block
|
|
9
9
|
return (
|
|
10
|
-
<div
|
|
10
|
+
<div>
|
|
11
11
|
<header className="mx-auto max-w-3xl space-y-6">
|
|
12
12
|
{categories && categories.length > 0 ? (
|
|
13
13
|
<div className="flex flex-wrap gap-2">
|
|
@@ -296,11 +296,11 @@ async function CollectionResults({
|
|
|
296
296
|
// every fetch behind it is cached, so it prerenders into the static shell.
|
|
297
297
|
export function ContentCollection(props: ContentCollectionProps) {
|
|
298
298
|
return (
|
|
299
|
-
<
|
|
299
|
+
<div className="flex flex-col gap-8">
|
|
300
300
|
<CollectionToolbar pinnedCategory={props.pinnedCategory} />
|
|
301
301
|
<Suspense fallback={<CollectionResultsSkeleton />}>
|
|
302
302
|
<CollectionResults {...props} />
|
|
303
303
|
</Suspense>
|
|
304
|
-
</
|
|
304
|
+
</div>
|
|
305
305
|
)
|
|
306
306
|
}
|
package/src/templates/next-pagebuilder-cache/components/page-builder/components/description.tsx
CHANGED
|
@@ -5,13 +5,9 @@ type DescriptionProps = {
|
|
|
5
5
|
}
|
|
6
6
|
|
|
7
7
|
export function Description({ block }: DescriptionProps) {
|
|
8
|
-
if (!block.description) return null
|
|
9
|
-
|
|
10
8
|
return (
|
|
11
|
-
<
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
</p>
|
|
15
|
-
</section>
|
|
9
|
+
<p className="max-w-[62ch] text-base/7 text-current/72 md:text-lg/8">
|
|
10
|
+
{block.description}
|
|
11
|
+
</p>
|
|
16
12
|
)
|
|
17
13
|
}
|
|
@@ -64,7 +64,7 @@ export function GatedContent({ block }: GatedContentProps) {
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
return (
|
|
67
|
-
<
|
|
67
|
+
<div>
|
|
68
68
|
{block.teaserHeading ? (
|
|
69
69
|
<h2 className="mb-4 font-semibold text-2xl tracking-tight md:text-4xl">
|
|
70
70
|
{block.teaserHeading}
|
|
@@ -114,7 +114,7 @@ export function GatedContent({ block }: GatedContentProps) {
|
|
|
114
114
|
</button>
|
|
115
115
|
</form>
|
|
116
116
|
)}
|
|
117
|
-
</
|
|
117
|
+
</div>
|
|
118
118
|
)
|
|
119
119
|
}
|
|
120
120
|
|
|
@@ -5,13 +5,9 @@ type HeroProps = {
|
|
|
5
5
|
}
|
|
6
6
|
|
|
7
7
|
export function Hero({ block }: HeroProps) {
|
|
8
|
-
if (!block.headline) return null
|
|
9
|
-
|
|
10
8
|
return (
|
|
11
|
-
<
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
</h1>
|
|
15
|
-
</section>
|
|
9
|
+
<h1 className="max-w-4xl font-semibold text-4xl tracking-tight md:text-6xl">
|
|
10
|
+
{block.headline}
|
|
11
|
+
</h1>
|
|
16
12
|
)
|
|
17
13
|
}
|
package/src/templates/next-pagebuilder-cache/components/page-builder/components/rich-text-block.tsx
CHANGED
|
@@ -1,16 +1,14 @@
|
|
|
1
|
-
import { RichText } from "@/components/sanity/rich-text"
|
|
2
1
|
import type { PageBuilderBlock } from "@/components/page-builder/types"
|
|
2
|
+
import { RichText } from "@/components/sanity/rich-text"
|
|
3
3
|
|
|
4
4
|
type RichTextBlockProps = {
|
|
5
5
|
block: Extract<PageBuilderBlock, { _type: "richTextBlock" }>
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
export function RichTextBlock({ block }: RichTextBlockProps) {
|
|
9
|
-
if (!block.content) return null
|
|
10
|
-
|
|
11
9
|
return (
|
|
12
|
-
<
|
|
10
|
+
<div className="prose max-w-none">
|
|
13
11
|
<RichText content={block.content} />
|
|
14
|
-
</
|
|
12
|
+
</div>
|
|
15
13
|
)
|
|
16
14
|
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { ReactNode } from "react"
|
|
2
|
+
import type { PageBuilderDocumentType } from "@/lib/integrations/sanity/page-builder-config"
|
|
3
|
+
import { ArticleContent } from "./components/article-content"
|
|
4
|
+
import { ContentCollection } from "./components/content-collection"
|
|
5
|
+
import type {
|
|
6
|
+
ContentCollectionSearchParams,
|
|
7
|
+
PinnedCollectionCategory,
|
|
8
|
+
} from "./components/content-collection/types"
|
|
9
|
+
import { Description } from "./components/description"
|
|
10
|
+
import { GatedContent } from "./components/gated-content"
|
|
11
|
+
import { Hero } from "./components/hero"
|
|
12
|
+
import { RichTextBlock } from "./components/rich-text-block"
|
|
13
|
+
import type { PageBuilderBlock } from "./types"
|
|
14
|
+
|
|
15
|
+
export type BlockContext = {
|
|
16
|
+
searchParams: ContentCollectionSearchParams
|
|
17
|
+
pinnedCategory: PinnedCollectionCategory
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
type BlockOf<T extends PageBuilderDocumentType> = Extract<
|
|
21
|
+
PageBuilderBlock,
|
|
22
|
+
{ _type: T }
|
|
23
|
+
>
|
|
24
|
+
|
|
25
|
+
type BlockEntry<T extends PageBuilderDocumentType> = {
|
|
26
|
+
render: (block: BlockOf<T>, context: BlockContext) => ReactNode
|
|
27
|
+
/** Skips the block *and* its `<Section>`, so no padded gap is left behind. */
|
|
28
|
+
isEmpty?: (block: BlockOf<T>) => boolean
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const blockRegistry: {
|
|
32
|
+
[T in PageBuilderDocumentType]: BlockEntry<T>
|
|
33
|
+
} = {
|
|
34
|
+
blogCollection: {
|
|
35
|
+
render: (block, { searchParams, pinnedCategory }) => (
|
|
36
|
+
<ContentCollection
|
|
37
|
+
block={block}
|
|
38
|
+
pinnedCategory={pinnedCategory}
|
|
39
|
+
searchParams={searchParams}
|
|
40
|
+
/>
|
|
41
|
+
),
|
|
42
|
+
},
|
|
43
|
+
blogContent: {
|
|
44
|
+
render: (block) => <ArticleContent block={block} />,
|
|
45
|
+
},
|
|
46
|
+
description: {
|
|
47
|
+
render: (block) => <Description block={block} />,
|
|
48
|
+
isEmpty: (block) => !block.description,
|
|
49
|
+
},
|
|
50
|
+
gatedContent: {
|
|
51
|
+
render: (block) => <GatedContent block={block} />,
|
|
52
|
+
},
|
|
53
|
+
hero: {
|
|
54
|
+
render: (block) => <Hero block={block} />,
|
|
55
|
+
isEmpty: (block) => !block.headline,
|
|
56
|
+
},
|
|
57
|
+
richTextBlock: {
|
|
58
|
+
render: (block) => <RichTextBlock block={block} />,
|
|
59
|
+
isEmpty: (block) => !block.content?.length,
|
|
60
|
+
},
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Indexing the registry with the union collapses its per-key signatures into an
|
|
65
|
+
* intersection nothing satisfies, so the widening happens once, here.
|
|
66
|
+
*/
|
|
67
|
+
export const getBlockEntry = (
|
|
68
|
+
type: PageBuilderDocumentType
|
|
69
|
+
): BlockEntry<PageBuilderDocumentType> | undefined =>
|
|
70
|
+
blockRegistry[type] as BlockEntry<PageBuilderDocumentType> | undefined
|