bsmnt 0.13.0 → 0.14.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 (90) hide show
  1. package/dist/modules/features/cms/sanity-pagebuilder/config.d.ts.map +1 -1
  2. package/dist/modules/features/cms/sanity-pagebuilder/config.js +1 -0
  3. package/dist/modules/features/cms/sanity-pagebuilder/config.js.map +1 -1
  4. package/dist/modules/features/cms/sanity-pagebuilder-cache/config.d.ts.map +1 -1
  5. package/dist/modules/features/cms/sanity-pagebuilder-cache/config.js +1 -0
  6. package/dist/modules/features/cms/sanity-pagebuilder-cache/config.js.map +1 -1
  7. package/package.json +1 -1
  8. package/src/agent-skills/ashby-pagebuilder-block/SKILL.md +29 -11
  9. package/src/agent-skills/hubspot-pagebuilder-block/SKILL.md +31 -12
  10. package/src/agent-skills/page-builder-block/SKILL.md +26 -13
  11. package/src/modules/features/cms/sanity-pagebuilder/files/app/api/revalidate/route.ts +2 -2
  12. package/src/modules/features/cms/sanity-pagebuilder/files/components/sanity/rich-text.tsx +1 -1
  13. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/block-actions-item.tsx +2 -2
  14. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/component-preview-frame.tsx +124 -0
  15. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/component-preview-pane.tsx +25 -0
  16. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/page-builder-input.tsx +3 -26
  17. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/page-builder-insert-menu.tsx +121 -93
  18. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/icons.ts +2 -2
  19. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/page-builder-config.ts +22 -50
  20. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/presentation.ts +1 -18
  21. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/queries.ts +56 -64
  22. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/sanity.config.ts +4 -8
  23. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/index.ts +0 -1
  24. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/reusable/blog-content.ts +2 -2
  25. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/index.ts +1 -4
  26. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/block.ts +4 -4
  27. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/page-builder.ts +4 -18
  28. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/page.ts +11 -37
  29. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/singletons.ts +0 -16
  30. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/structure.ts +35 -33
  31. package/src/modules/features/cms/sanity-pagebuilder/files/lib/utils/studio-embed.ts +22 -0
  32. package/src/modules/features/cms/sanity-pagebuilder-cache/files/app/api/revalidate/route.ts +2 -2
  33. package/src/modules/features/cms/sanity-pagebuilder-cache/files/components/sanity/rich-text.tsx +1 -1
  34. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/block-actions-item.tsx +2 -2
  35. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/component-preview-frame.tsx +124 -0
  36. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/component-preview-pane.tsx +25 -0
  37. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/page-builder-input.tsx +3 -26
  38. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/page-builder-insert-menu.tsx +121 -93
  39. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/icons.ts +2 -2
  40. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/page-builder-config.ts +22 -50
  41. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/presentation.ts +1 -18
  42. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/queries.ts +56 -64
  43. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/sanity.config.ts +4 -8
  44. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/index.ts +0 -1
  45. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/reusable/blog-content.ts +2 -2
  46. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/index.ts +1 -4
  47. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/block.ts +4 -4
  48. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/page-builder.ts +4 -18
  49. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/page.ts +11 -37
  50. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/singletons.ts +0 -16
  51. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/structure.ts +35 -33
  52. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/utils/studio-embed.ts +22 -0
  53. package/src/modules/features/forms/hubspot/files/_sanity-adapter/form-block.ts +4 -10
  54. package/src/modules/features/forms/hubspot/files/lib/integrations/hubspot/README.md +10 -5
  55. package/src/modules/features/jobs/ashby/files/_sanity-adapter/job-board-block.ts +0 -7
  56. package/src/modules/features/jobs/ashby/files/lib/integrations/ashby/README.md +11 -5
  57. package/src/templates/next-default/lib/utils/README.md +1 -0
  58. package/src/templates/next-default/lib/utils/image-sizes.ts +64 -25
  59. package/src/templates/next-experiments/lib/utils/README.md +1 -0
  60. package/src/templates/next-experiments/lib/utils/image-sizes.ts +64 -25
  61. package/src/templates/next-pagebuilder/AGENTS.md +19 -7
  62. package/src/templates/next-pagebuilder/README.md +15 -3
  63. package/src/templates/next-pagebuilder/app/actions/unlock-gated-content.ts +5 -4
  64. package/src/templates/next-pagebuilder/app/components/page.tsx +81 -0
  65. package/src/templates/next-pagebuilder/components/page-builder/components/gated-content/index.tsx +8 -3
  66. package/src/templates/next-pagebuilder/components/page-builder/library.ts +183 -0
  67. package/src/templates/next-pagebuilder/components/page-builder/registry.tsx +4 -1
  68. package/src/templates/next-pagebuilder/components/page-builder/renderer.tsx +3 -1
  69. package/src/templates/next-pagebuilder/components/page-builder/stage.tsx +10 -0
  70. package/src/templates/next-pagebuilder/components/page-document/index.tsx +2 -1
  71. package/src/templates/next-pagebuilder/lib/integrations/sanity/sanity.types.ts +139 -271
  72. package/src/templates/next-pagebuilder/lib/utils/image-sizes.ts +64 -25
  73. package/src/templates/next-pagebuilder-cache/AGENTS.md +19 -7
  74. package/src/templates/next-pagebuilder-cache/README.md +15 -3
  75. package/src/templates/next-pagebuilder-cache/app/actions/unlock-gated-content.ts +5 -4
  76. package/src/templates/next-pagebuilder-cache/app/components/page.tsx +91 -0
  77. package/src/templates/next-pagebuilder-cache/components/page-builder/components/gated-content/index.tsx +8 -3
  78. package/src/templates/next-pagebuilder-cache/components/page-builder/library.ts +183 -0
  79. package/src/templates/next-pagebuilder-cache/components/page-builder/registry.tsx +4 -1
  80. package/src/templates/next-pagebuilder-cache/components/page-builder/renderer.tsx +3 -1
  81. package/src/templates/next-pagebuilder-cache/components/page-builder/stage.tsx +10 -0
  82. package/src/templates/next-pagebuilder-cache/components/page-document/index.tsx +2 -1
  83. package/src/templates/next-pagebuilder-cache/lib/integrations/sanity/sanity.types.ts +139 -271
  84. package/src/templates/next-pagebuilder-cache/lib/utils/image-sizes.ts +64 -25
  85. package/src/templates/next-webgl/lib/utils/README.md +1 -0
  86. package/src/templates/next-webgl/lib/utils/image-sizes.ts +64 -25
  87. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/use-studio-preview-urls.ts +0 -62
  88. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/studio-preview.ts +0 -37
  89. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/use-studio-preview-urls.ts +0 -62
  90. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/studio-preview.ts +0 -37
@@ -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 toSizes(
28
- base: string,
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 [...conditions, base].join(", ")
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>, breakpoints?: B): string {
62
+ >(widths: ByBreakpoint<string, B>, options: ImageSizesOptions<B> = {}): string {
50
63
  const { base, ...overrides } = widths
51
- return toSizes(base, overrides, breakpoints ?? BASEMENT_BREAKPOINTS)
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
- >(columns: ByBreakpoint<number, B>, breakpoints?: B): string {
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 widths: Record<string, string> = {}
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
- for (const [name, count] of Object.entries(overrides)) {
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 toSizes(columnWidth(base), widths, breakpoints ?? BASEMENT_BREAKPOINTS)
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
  }
@@ -45,6 +45,7 @@ export const metadata = generatePageMetadata({ title: 'About' })
45
45
  // next/image sizes — keys mirror the element's Tailwind variants
46
46
  buildImageSizes({ base: '100vw', desktop: '796px' })
47
47
  buildGridImageSizes({ base: 1, tablet: 2, desktop: 3 })
48
+ buildGridImageSizes({ base: 1, tablet: 2, desktop: 3 }, { maxWidth: 1440 })
48
49
 
49
50
  // Refs — in a component, use the hook: an inline mergeRefs([...]) is a new
50
51
  // callback each render, so React detaches and reattaches every ref.
@@ -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 toSizes(
28
- base: string,
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 [...conditions, base].join(", ")
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>, breakpoints?: B): string {
62
+ >(widths: ByBreakpoint<string, B>, options: ImageSizesOptions<B> = {}): string {
50
63
  const { base, ...overrides } = widths
51
- return toSizes(base, overrides, breakpoints ?? BASEMENT_BREAKPOINTS)
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
- >(columns: ByBreakpoint<number, B>, breakpoints?: B): string {
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 widths: Record<string, string> = {}
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
- for (const [name, count] of Object.entries(overrides)) {
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 toSizes(columnWidth(base), widths, breakpoints ?? BASEMENT_BREAKPOINTS)
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
  }
@@ -1,62 +0,0 @@
1
- import { useEffect, useState } from "react";
2
- import { useClient } from "sanity";
3
- import { apiVersion } from "@/lib/integrations/sanity/env";
4
-
5
- const PREVIEW_ASSETS_QUERY = `*[
6
- _type == "studioPreview" &&
7
- key in $keys &&
8
- defined(preview.asset._ref)
9
- ] | order(_updatedAt desc) {
10
- key,
11
- "url": preview.asset->url
12
- }`;
13
-
14
- type PreviewDocument = {
15
- key: string | null;
16
- url: string | null;
17
- };
18
-
19
- export type StudioPreviewUrls = Record<string, string>;
20
-
21
- const SEPARATOR = " ";
22
-
23
- /** `undefined` while in flight, so callers can tell loading from "no preview". */
24
- export function useStudioPreviewUrls(keys: readonly string[]) {
25
- const client = useClient({ apiVersion });
26
- const [urls, setUrls] = useState<StudioPreviewUrls>();
27
- // Keys off contents, not array identity — callers pass a freshly mapped array.
28
- const keyList = keys.join(SEPARATOR);
29
-
30
- useEffect(() => {
31
- if (!keyList) {
32
- setUrls({});
33
- return;
34
- }
35
-
36
- let cancelled = false;
37
-
38
- client
39
- .fetch<PreviewDocument[]>(PREVIEW_ASSETS_QUERY, {
40
- keys: keyList.split(SEPARATOR),
41
- })
42
- .then((documents) => {
43
- if (cancelled) return;
44
- const next: StudioPreviewUrls = {};
45
- for (const document of documents) {
46
- if (document.key && document.url && !next[document.key]) {
47
- next[document.key] = document.url;
48
- }
49
- }
50
- setUrls(next);
51
- })
52
- .catch(() => {
53
- if (!cancelled) setUrls({});
54
- });
55
-
56
- return () => {
57
- cancelled = true;
58
- };
59
- }, [client, keyList]);
60
-
61
- return urls;
62
- }
@@ -1,37 +0,0 @@
1
- import { defineField, defineType } from "sanity";
2
- import { pageBuilderCardIcon } from "@/lib/integrations/sanity/icons";
3
- import { pageBuilderBlockPreviewOptions } from "@/lib/integrations/sanity/page-builder-config";
4
-
5
- export const studioPreview = defineType({
6
- name: "studioPreview",
7
- title: "Component Preview",
8
- type: "document",
9
- icon: pageBuilderCardIcon,
10
- fields: [
11
- defineField({
12
- name: "key",
13
- title: "Component",
14
- type: "string",
15
- options: { list: pageBuilderBlockPreviewOptions },
16
- validation: (Rule) => Rule.required(),
17
- }),
18
- defineField({
19
- name: "preview",
20
- title: "Preview image",
21
- type: "image",
22
- options: { hotspot: true },
23
- validation: (Rule) => Rule.required(),
24
- }),
25
- ],
26
- preview: {
27
- select: { key: "key", media: "preview" },
28
- prepare({ key, media }) {
29
- return {
30
- title:
31
- pageBuilderBlockPreviewOptions.find((option) => option.value === key)
32
- ?.title ?? "Component Preview",
33
- media,
34
- };
35
- },
36
- },
37
- });
@@ -1,62 +0,0 @@
1
- import { useEffect, useState } from "react";
2
- import { useClient } from "sanity";
3
- import { apiVersion } from "@/lib/integrations/sanity/env";
4
-
5
- const PREVIEW_ASSETS_QUERY = `*[
6
- _type == "studioPreview" &&
7
- key in $keys &&
8
- defined(preview.asset._ref)
9
- ] | order(_updatedAt desc) {
10
- key,
11
- "url": preview.asset->url
12
- }`;
13
-
14
- type PreviewDocument = {
15
- key: string | null;
16
- url: string | null;
17
- };
18
-
19
- export type StudioPreviewUrls = Record<string, string>;
20
-
21
- const SEPARATOR = " ";
22
-
23
- /** `undefined` while in flight, so callers can tell loading from "no preview". */
24
- export function useStudioPreviewUrls(keys: readonly string[]) {
25
- const client = useClient({ apiVersion });
26
- const [urls, setUrls] = useState<StudioPreviewUrls>();
27
- // Keys off contents, not array identity — callers pass a freshly mapped array.
28
- const keyList = keys.join(SEPARATOR);
29
-
30
- useEffect(() => {
31
- if (!keyList) {
32
- setUrls({});
33
- return;
34
- }
35
-
36
- let cancelled = false;
37
-
38
- client
39
- .fetch<PreviewDocument[]>(PREVIEW_ASSETS_QUERY, {
40
- keys: keyList.split(SEPARATOR),
41
- })
42
- .then((documents) => {
43
- if (cancelled) return;
44
- const next: StudioPreviewUrls = {};
45
- for (const document of documents) {
46
- if (document.key && document.url && !next[document.key]) {
47
- next[document.key] = document.url;
48
- }
49
- }
50
- setUrls(next);
51
- })
52
- .catch(() => {
53
- if (!cancelled) setUrls({});
54
- });
55
-
56
- return () => {
57
- cancelled = true;
58
- };
59
- }, [client, keyList]);
60
-
61
- return urls;
62
- }
@@ -1,37 +0,0 @@
1
- import { defineField, defineType } from "sanity";
2
- import { pageBuilderCardIcon } from "@/lib/integrations/sanity/icons";
3
- import { pageBuilderBlockPreviewOptions } from "@/lib/integrations/sanity/page-builder-config";
4
-
5
- export const studioPreview = defineType({
6
- name: "studioPreview",
7
- title: "Component Preview",
8
- type: "document",
9
- icon: pageBuilderCardIcon,
10
- fields: [
11
- defineField({
12
- name: "key",
13
- title: "Component",
14
- type: "string",
15
- options: { list: pageBuilderBlockPreviewOptions },
16
- validation: (Rule) => Rule.required(),
17
- }),
18
- defineField({
19
- name: "preview",
20
- title: "Preview image",
21
- type: "image",
22
- options: { hotspot: true },
23
- validation: (Rule) => Rule.required(),
24
- }),
25
- ],
26
- preview: {
27
- select: { key: "key", media: "preview" },
28
- prepare({ key, media }) {
29
- return {
30
- title:
31
- pageBuilderBlockPreviewOptions.find((option) => option.value === key)
32
- ?.title ?? "Component Preview",
33
- media,
34
- };
35
- },
36
- },
37
- });