bsmnt 0.13.1 → 0.15.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 (177) hide show
  1. package/dist/core/create/internal-ids.d.ts +1 -0
  2. package/dist/core/create/internal-ids.d.ts.map +1 -1
  3. package/dist/core/create/internal-ids.js +1 -0
  4. package/dist/core/create/internal-ids.js.map +1 -1
  5. package/dist/domain/skills.d.ts +5 -0
  6. package/dist/domain/skills.d.ts.map +1 -1
  7. package/dist/domain/skills.js +9 -1
  8. package/dist/domain/skills.js.map +1 -1
  9. package/dist/infrastructure/create/format-project.d.ts +9 -0
  10. package/dist/infrastructure/create/format-project.d.ts.map +1 -0
  11. package/dist/infrastructure/create/format-project.js +21 -0
  12. package/dist/infrastructure/create/format-project.js.map +1 -0
  13. package/dist/infrastructure/create/lifecycle-registry.d.ts.map +1 -1
  14. package/dist/infrastructure/create/lifecycle-registry.js +5 -0
  15. package/dist/infrastructure/create/lifecycle-registry.js.map +1 -1
  16. package/dist/modules/features/cms/sanity-pagebuilder/config.d.ts.map +1 -1
  17. package/dist/modules/features/cms/sanity-pagebuilder/config.js +1 -0
  18. package/dist/modules/features/cms/sanity-pagebuilder/config.js.map +1 -1
  19. package/dist/modules/features/cms/sanity-pagebuilder/index.d.ts.map +1 -1
  20. package/dist/modules/features/cms/sanity-pagebuilder/index.js +1 -0
  21. package/dist/modules/features/cms/sanity-pagebuilder/index.js.map +1 -1
  22. package/dist/modules/features/cms/sanity-pagebuilder-cache/config.d.ts.map +1 -1
  23. package/dist/modules/features/cms/sanity-pagebuilder-cache/config.js +1 -0
  24. package/dist/modules/features/cms/sanity-pagebuilder-cache/config.js.map +1 -1
  25. package/dist/modules/features/cms/sanity-pagebuilder-cache/index.d.ts.map +1 -1
  26. package/dist/modules/features/cms/sanity-pagebuilder-cache/index.js +1 -0
  27. package/dist/modules/features/cms/sanity-pagebuilder-cache/index.js.map +1 -1
  28. package/package.json +1 -1
  29. package/src/agent-skills/ashby-pagebuilder-block/SKILL.md +29 -11
  30. package/src/agent-skills/hubspot-pagebuilder-block/SKILL.md +31 -12
  31. package/src/agent-skills/launch-ready/SKILL.md +114 -0
  32. package/src/agent-skills/launch-ready/references/a11y.md +74 -0
  33. package/src/agent-skills/launch-ready/references/content.md +65 -0
  34. package/src/agent-skills/launch-ready/references/crawlers.md +89 -0
  35. package/src/agent-skills/launch-ready/references/launch.md +44 -0
  36. package/src/agent-skills/launch-ready/references/metadata.md +83 -0
  37. package/src/agent-skills/launch-ready/references/performance.md +36 -0
  38. package/src/agent-skills/launch-ready/references/routing.md +47 -0
  39. package/src/agent-skills/launch-ready/references/security.md +39 -0
  40. package/src/agent-skills/page-builder-block/SKILL.md +26 -13
  41. package/src/modules/features/cms/sanity/files/app/layout.tsx +2 -2
  42. package/src/modules/features/cms/sanity/files/lib/utils/metadata.ts +2 -2
  43. package/src/modules/features/cms/sanity/files/lib/utils/url.ts +6 -3
  44. package/src/modules/features/cms/sanity-pagebuilder/files/app/api/revalidate/route.ts +2 -2
  45. package/src/modules/features/cms/sanity-pagebuilder/files/components/sanity/rich-text.tsx +4 -2
  46. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/block-actions-item.tsx +2 -2
  47. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/component-preview-frame.tsx +124 -0
  48. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/component-preview-pane.tsx +25 -0
  49. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/page-builder-input.tsx +3 -26
  50. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/page-builder-insert-menu.tsx +121 -93
  51. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/redirect-import-dialog.tsx +538 -0
  52. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/csv.ts +88 -0
  53. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/fetchers/breadcrumbs.ts +22 -0
  54. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/icons.ts +2 -2
  55. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/page-builder-config.ts +36 -49
  56. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/presentation.ts +1 -50
  57. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/queries.ts +76 -64
  58. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/redirect-import.ts +651 -0
  59. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/sanity.config.ts +6 -8
  60. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/index.ts +0 -1
  61. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/index.ts +1 -4
  62. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/layout/redirect.ts +9 -6
  63. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/layout/structured-data.ts +8 -0
  64. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/block.ts +4 -4
  65. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/metadata.ts +60 -6
  66. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/page-builder.ts +4 -18
  67. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/page.ts +33 -38
  68. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/richText.ts +0 -1
  69. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/singletons.ts +0 -16
  70. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/structure.ts +52 -35
  71. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/tags.ts +5 -2
  72. package/src/modules/features/cms/sanity-pagebuilder/files/lib/scripts/sanity-typegen.ts +8 -11
  73. package/src/modules/features/cms/sanity-pagebuilder/files/lib/utils/metadata.ts +109 -136
  74. package/src/modules/features/cms/sanity-pagebuilder/files/lib/utils/studio-embed.ts +22 -0
  75. package/src/modules/features/cms/sanity-pagebuilder-cache/files/app/api/revalidate/route.ts +2 -2
  76. package/src/modules/features/cms/sanity-pagebuilder-cache/files/components/sanity/rich-text.tsx +4 -2
  77. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/block-actions-item.tsx +2 -2
  78. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/component-preview-frame.tsx +124 -0
  79. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/component-preview-pane.tsx +25 -0
  80. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/page-builder-input.tsx +3 -26
  81. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/page-builder-insert-menu.tsx +121 -93
  82. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/redirect-import-dialog.tsx +538 -0
  83. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/csv.ts +88 -0
  84. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/fetchers/breadcrumbs.ts +25 -0
  85. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/icons.ts +2 -2
  86. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/page-builder-config.ts +36 -49
  87. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/presentation.ts +1 -50
  88. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/queries.ts +76 -64
  89. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/redirect-import.ts +651 -0
  90. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/sanity.config.ts +6 -8
  91. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/index.ts +0 -1
  92. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/index.ts +1 -4
  93. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/layout/redirect.ts +9 -6
  94. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/layout/structured-data.ts +8 -0
  95. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/block.ts +4 -4
  96. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/metadata.ts +60 -6
  97. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/page-builder.ts +4 -18
  98. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/page.ts +33 -38
  99. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/richText.ts +0 -1
  100. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/singletons.ts +0 -16
  101. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/structure.ts +52 -35
  102. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/tags.ts +6 -2
  103. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/scripts/sanity-typegen.ts +8 -11
  104. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/utils/metadata.ts +109 -136
  105. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/utils/studio-embed.ts +22 -0
  106. package/src/modules/features/forms/hubspot/files/_sanity-adapter/form-block.ts +4 -10
  107. package/src/modules/features/forms/hubspot/files/lib/integrations/hubspot/README.md +10 -5
  108. package/src/modules/features/jobs/ashby/files/_sanity-adapter/job-board-block.ts +0 -7
  109. package/src/modules/features/jobs/ashby/files/lib/integrations/ashby/README.md +11 -5
  110. package/src/templates/next-default/.env.example +3 -1
  111. package/src/templates/next-default/app/layout.tsx +2 -1
  112. package/src/templates/next-default/app/robots.ts +9 -3
  113. package/src/templates/next-default/lib/utils/metadata.ts +2 -1
  114. package/src/templates/next-default/lib/utils/url.ts +20 -3
  115. package/src/templates/next-default/next.config.ts +16 -0
  116. package/src/templates/next-experiments/.env.example +3 -1
  117. package/src/templates/next-experiments/app/layout.tsx +2 -1
  118. package/src/templates/next-experiments/app/robots.ts +9 -3
  119. package/src/templates/next-experiments/lib/utils/metadata.ts +2 -1
  120. package/src/templates/next-experiments/lib/utils/url.ts +20 -3
  121. package/src/templates/next-experiments/next.config.ts +16 -0
  122. package/src/templates/next-pagebuilder/.env.example +3 -1
  123. package/src/templates/next-pagebuilder/.github/workflows/ci.yml +32 -8
  124. package/src/templates/next-pagebuilder/AGENTS.md +22 -7
  125. package/src/templates/next-pagebuilder/README.md +15 -3
  126. package/src/templates/next-pagebuilder/app/(content)/[[...slug]]/page.tsx +45 -7
  127. package/src/templates/next-pagebuilder/app/actions/unlock-gated-content.ts +5 -4
  128. package/src/templates/next-pagebuilder/app/components/page.tsx +81 -0
  129. package/src/templates/next-pagebuilder/app/layout.tsx +3 -41
  130. package/src/templates/next-pagebuilder/app/robots.ts +9 -3
  131. package/src/templates/next-pagebuilder/biome.json +2 -1
  132. package/src/templates/next-pagebuilder/components/layout/footer/index.tsx +1 -1
  133. package/src/templates/next-pagebuilder/components/page-builder/components/gated-content/index.tsx +8 -3
  134. package/src/templates/next-pagebuilder/components/page-builder/library.ts +183 -0
  135. package/src/templates/next-pagebuilder/components/page-builder/registry.tsx +4 -1
  136. package/src/templates/next-pagebuilder/components/page-builder/renderer.tsx +3 -1
  137. package/src/templates/next-pagebuilder/components/page-builder/stage.tsx +10 -0
  138. package/src/templates/next-pagebuilder/components/page-document/index.tsx +28 -35
  139. package/src/templates/next-pagebuilder/lefthook.yml +1 -1
  140. package/src/templates/next-pagebuilder/lib/integrations/sanity/sanity.types.ts +170 -299
  141. package/src/templates/next-pagebuilder/lib/utils/base-url.ts +18 -3
  142. package/src/templates/next-pagebuilder/lib/utils/breadcrumbs.ts +70 -0
  143. package/src/templates/next-pagebuilder/next.config.ts +16 -0
  144. package/src/templates/next-pagebuilder/package.json +1 -1
  145. package/src/templates/next-pagebuilder-cache/.env.example +3 -1
  146. package/src/templates/next-pagebuilder-cache/.github/workflows/ci.yml +32 -8
  147. package/src/templates/next-pagebuilder-cache/AGENTS.md +22 -7
  148. package/src/templates/next-pagebuilder-cache/README.md +15 -3
  149. package/src/templates/next-pagebuilder-cache/app/(content)/[[...slug]]/page.tsx +46 -7
  150. package/src/templates/next-pagebuilder-cache/app/actions/unlock-gated-content.ts +5 -4
  151. package/src/templates/next-pagebuilder-cache/app/components/page.tsx +91 -0
  152. package/src/templates/next-pagebuilder-cache/app/layout.tsx +5 -46
  153. package/src/templates/next-pagebuilder-cache/app/robots.ts +9 -3
  154. package/src/templates/next-pagebuilder-cache/biome.json +2 -1
  155. package/src/templates/next-pagebuilder-cache/components/layout/footer/index.tsx +1 -1
  156. package/src/templates/next-pagebuilder-cache/components/page-builder/components/gated-content/index.tsx +8 -3
  157. package/src/templates/next-pagebuilder-cache/components/page-builder/library.ts +183 -0
  158. package/src/templates/next-pagebuilder-cache/components/page-builder/registry.tsx +4 -1
  159. package/src/templates/next-pagebuilder-cache/components/page-builder/renderer.tsx +3 -1
  160. package/src/templates/next-pagebuilder-cache/components/page-builder/stage.tsx +10 -0
  161. package/src/templates/next-pagebuilder-cache/components/page-document/index.tsx +28 -35
  162. package/src/templates/next-pagebuilder-cache/lefthook.yml +1 -1
  163. package/src/templates/next-pagebuilder-cache/lib/integrations/sanity/sanity.types.ts +170 -299
  164. package/src/templates/next-pagebuilder-cache/lib/utils/base-url.ts +18 -3
  165. package/src/templates/next-pagebuilder-cache/lib/utils/breadcrumbs.ts +70 -0
  166. package/src/templates/next-pagebuilder-cache/next.config.ts +16 -0
  167. package/src/templates/next-pagebuilder-cache/package.json +1 -1
  168. package/src/templates/next-webgl/.env.example +3 -1
  169. package/src/templates/next-webgl/app/layout.tsx +2 -1
  170. package/src/templates/next-webgl/app/robots.ts +9 -3
  171. package/src/templates/next-webgl/lib/utils/metadata.ts +2 -1
  172. package/src/templates/next-webgl/lib/utils/url.ts +20 -3
  173. package/src/templates/next-webgl/next.config.ts +16 -0
  174. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/use-studio-preview-urls.ts +0 -62
  175. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/studio-preview.ts +0 -37
  176. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/use-studio-preview-urls.ts +0 -62
  177. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/studio-preview.ts +0 -37
@@ -6,7 +6,7 @@ import { apiVersion } from "@/lib/integrations/sanity/env";
6
6
 
7
7
  const HIDDEN_KEYS_FIELD = "hiddenPageBuilderKeys";
8
8
 
9
- type ReferenceItem = { _key: string; _ref: string; _type: string };
9
+ type BlockItem = { _key: string; _type?: string };
10
10
 
11
11
  /**
12
12
  * Eye-icon toggle for a page builder placement's per-page visibility. Adds the
@@ -14,7 +14,7 @@ type ReferenceItem = { _key: string; _ref: string; _type: string };
14
14
  * filters those keys out at query time. Standalone control because Sanity's
15
15
  * built-in item menu isn't extensible via public props.
16
16
  */
17
- export function BlockActionsItem(props: ObjectItemProps<ReferenceItem>) {
17
+ export function BlockActionsItem(props: ObjectItemProps<BlockItem>) {
18
18
  const { value, renderDefault } = props;
19
19
  const client = useClient({ apiVersion });
20
20
  const toast = useToast();
@@ -0,0 +1,124 @@
1
+ import { Box, Button, Card, Code, Flex, Text } from "@sanity/ui";
2
+ import { useEffect, useRef, useState } from "react";
3
+
4
+ const VIEWPORTS = [
5
+ { id: "desktop", label: "Desktop", width: 1440 },
6
+ { id: "tablet", label: "Tablet", width: 834 },
7
+ { id: "mobile", label: "Mobile", width: 390 },
8
+ ] as const;
9
+
10
+ type ViewportId = (typeof VIEWPORTS)[number]["id"];
11
+
12
+ interface ComponentPreviewFrameProps {
13
+ notes?: string | undefined;
14
+ path: string;
15
+ }
16
+
17
+ export const ComponentPreviewFrame = ({
18
+ notes,
19
+ path,
20
+ }: ComponentPreviewFrameProps) => {
21
+ const stage = useRef<HTMLDivElement>(null);
22
+ const [box, setBox] = useState({ height: 0, width: 0 });
23
+ const [viewport, setViewport] = useState<ViewportId>("desktop");
24
+
25
+ useEffect(() => {
26
+ const element = stage.current;
27
+ if (!element) return;
28
+
29
+ const observer = new ResizeObserver(([entry]) => {
30
+ if (!entry) return;
31
+ const { height, width } = entry.contentRect;
32
+ setBox({ height, width });
33
+ });
34
+
35
+ observer.observe(element);
36
+ return () => observer.disconnect();
37
+ }, []);
38
+
39
+ const width = VIEWPORTS.find((item) => item.id === viewport)?.width ?? 1440;
40
+
41
+ const scale = box.width > 0 ? Math.min(1, box.width / width) : 1;
42
+ const height = box.height > 0 ? box.height / scale : 0;
43
+
44
+ return (
45
+ <Flex direction="column" height="fill">
46
+ <Card borderBottom padding={2} tone="transparent">
47
+ <Flex align="center" gap={2}>
48
+ <Flex gap={1}>
49
+ {VIEWPORTS.map((item) => (
50
+ <Button
51
+ fontSize={0}
52
+ key={item.id}
53
+ mode={item.id === viewport ? "default" : "bleed"}
54
+ onClick={() => setViewport(item.id)}
55
+ padding={2}
56
+ text={item.label}
57
+ tone={item.id === viewport ? "primary" : "default"}
58
+ />
59
+ ))}
60
+ </Flex>
61
+ <Box flex={1} />
62
+ <Code size={0}>
63
+ {width} × {Math.round(height)} · {Math.round(scale * 100)}%
64
+ </Code>
65
+ </Flex>
66
+ </Card>
67
+
68
+ {notes ? (
69
+ <Card borderBottom padding={3} tone="transparent">
70
+ <Text muted size={0}>
71
+ {notes}
72
+ </Text>
73
+ </Card>
74
+ ) : null}
75
+
76
+ <Box
77
+ flex={1}
78
+ style={{
79
+ background: "var(--card-muted-bg-color)",
80
+ overflow: "hidden",
81
+ position: "relative",
82
+ }}
83
+ >
84
+ <div
85
+ ref={stage}
86
+ style={{
87
+ display: "flex",
88
+ height: "100%",
89
+ justifyContent: "center",
90
+ width: "100%",
91
+ }}
92
+ >
93
+ {height > 0 ? (
94
+ <div
95
+ style={{
96
+ background: "var(--card-bg-color)",
97
+ border: "1px solid var(--card-border-color)",
98
+ borderRadius: 4,
99
+ boxShadow: "0 1px 12px rgb(0 0 0 / 0.12)",
100
+ flex: "none",
101
+ height: box.height,
102
+ overflow: "hidden",
103
+ width: width * scale,
104
+ }}
105
+ >
106
+ <iframe
107
+ src={path}
108
+ style={{
109
+ border: 0,
110
+ display: "block",
111
+ height,
112
+ transform: `scale(${scale})`,
113
+ transformOrigin: "top left",
114
+ width,
115
+ }}
116
+ title="Component preview"
117
+ />
118
+ </div>
119
+ ) : null}
120
+ </div>
121
+ </Box>
122
+ </Flex>
123
+ );
124
+ };
@@ -0,0 +1,25 @@
1
+ import { Card, Text } from "@sanity/ui";
2
+ import { ComponentPreviewFrame } from "./component-preview-frame";
3
+
4
+ interface ComponentPreviewPaneProps {
5
+ options?: Record<string, unknown>;
6
+ }
7
+
8
+ export const ComponentPreviewPane = ({
9
+ options,
10
+ }: ComponentPreviewPaneProps) => {
11
+ const path = options?.path as string | undefined;
12
+ const notes = options?.notes as string | undefined;
13
+
14
+ if (!path) {
15
+ return (
16
+ <Card padding={4}>
17
+ <Text muted size={1}>
18
+ This component has no preview route.
19
+ </Text>
20
+ </Card>
21
+ );
22
+ }
23
+
24
+ return <ComponentPreviewFrame notes={notes} path={path} />;
25
+ };
@@ -1,38 +1,18 @@
1
1
  import { type ArrayOfObjectsInputProps, useFormValue } from "sanity";
2
2
  import { PageBuilderInsertMenu } from "@/lib/integrations/sanity/components/page-builder-insert-menu";
3
- import { useStudioPreviewUrls } from "@/lib/integrations/sanity/components/use-studio-preview-urls";
4
3
  import {
5
- blockPreviewKey,
6
4
  getVisiblePageBuilderInsertMenuGroups,
7
5
  getVisiblePageBuilderMemberNames,
6
+ type PageBuilderDocumentType,
8
7
  type PageBuilderGroupName,
9
- pageBuilderReferenceMembers,
10
8
  } from "@/lib/integrations/sanity/page-builder-config";
11
9
 
12
- const BLOCK_PREVIEW_KEYS = pageBuilderReferenceMembers.map((member) =>
13
- blockPreviewKey(member.name),
14
- );
15
-
16
- function useInsertMenuViews() {
17
- const previewUrls = useStudioPreviewUrls(BLOCK_PREVIEW_KEYS);
18
-
19
- return [
20
- {
21
- name: "grid" as const,
22
- previewImageUrl: (schemaTypeName: string) =>
23
- previewUrls?.[blockPreviewKey(schemaTypeName)],
24
- },
25
- { name: "list" as const },
26
- ];
27
- }
28
-
29
10
  export function PageBuilderInput(props: ArrayOfObjectsInputProps) {
30
11
  const pageTypeValue = useFormValue(["type"]);
31
12
  const pageType =
32
13
  typeof pageTypeValue === "string"
33
14
  ? (pageTypeValue as PageBuilderGroupName)
34
15
  : undefined;
35
- const views = useInsertMenuViews();
36
16
  const visibleMemberNames = getVisiblePageBuilderMemberNames(pageType);
37
17
 
38
18
  return props.renderDefault({
@@ -40,17 +20,14 @@ export function PageBuilderInput(props: ArrayOfObjectsInputProps) {
40
20
  arrayFunctions: PageBuilderInsertMenu,
41
21
  schemaType: {
42
22
  ...props.schemaType,
43
- of: props.schemaType.of.filter(
44
- (member) =>
45
- typeof member.name === "string" &&
46
- visibleMemberNames.has(member.name),
23
+ of: props.schemaType.of.filter((member) =>
24
+ visibleMemberNames.has(member.name as PageBuilderDocumentType),
47
25
  ),
48
26
  options: {
49
27
  ...props.schemaType.options,
50
28
  insertMenu: {
51
29
  ...props.schemaType.options?.insertMenu,
52
30
  groups: getVisiblePageBuilderInsertMenuGroups(pageType),
53
- views,
54
31
  },
55
32
  },
56
33
  },
@@ -12,6 +12,11 @@ import {
12
12
  } from "@sanity/ui";
13
13
  import { useMemo, useState } from "react";
14
14
  import type { ArrayInputFunctionsProps, ArraySchemaType } from "sanity";
15
+ import {
16
+ getComponentPreviewPath,
17
+ getLibraryEntry,
18
+ } from "@/components/page-builder/library";
19
+ import { ComponentPreviewFrame } from "./component-preview-frame";
15
20
 
16
21
  type ArrayItem = { _key: string };
17
22
  type MemberSchemaType = ArraySchemaType["of"][number];
@@ -24,18 +29,22 @@ type InsertMenuSection = {
24
29
  members: MemberSchemaType[];
25
30
  };
26
31
 
27
- const SCROLL_HEIGHT = "min(60vh, 34rem)";
28
- const TILE_ASPECT_RATIO = "3 / 2";
32
+ const DIALOG_HEIGHT = "min(70vh, 42rem)";
33
+ const CHOICE_LIST_WIDTH = 260;
29
34
 
30
35
  const memberTitle = (member: MemberSchemaType) => member.title ?? member.name;
31
36
 
32
- function BlockTile({
37
+ function ChoiceRow({
38
+ active,
33
39
  member,
34
- previewUrl,
40
+ notes,
41
+ onAdd,
35
42
  onSelect,
36
43
  }: {
44
+ active: boolean;
37
45
  member: MemberSchemaType;
38
- previewUrl: string | undefined;
46
+ notes: string | undefined;
47
+ onAdd: () => void;
39
48
  onSelect: () => void;
40
49
  }) {
41
50
  const Icon = member.icon;
@@ -43,63 +52,42 @@ function BlockTile({
43
52
  return (
44
53
  <Card
45
54
  as="button"
46
- border
47
55
  onClick={onSelect}
48
- overflow="hidden"
49
- padding={0}
56
+ onDoubleClick={onAdd}
57
+ padding={2}
58
+ pressed={active}
50
59
  radius={2}
51
60
  style={{ cursor: "pointer", textAlign: "left", width: "100%" }}
61
+ tone={active ? "primary" : "default"}
52
62
  type="button"
53
63
  >
54
- <Stack space={0}>
55
- <Box
56
- style={{
57
- aspectRatio: TILE_ASPECT_RATIO,
58
- background: "var(--card-muted-bg-color)",
59
- position: "relative",
60
- }}
61
- >
62
- {previewUrl ? (
63
- // biome-ignore lint/performance/noImgElement: Studio-only CMS asset
64
- <img
65
- alt=""
66
- src={`${previewUrl}?w=640&fit=max&auto=format`}
67
- style={{
68
- height: "100%",
69
- inset: 0,
70
- objectFit: "contain",
71
- position: "absolute",
72
- width: "100%",
73
- }}
74
- />
75
- ) : (
76
- <Flex
77
- align="center"
78
- justify="center"
79
- style={{ inset: 0, position: "absolute" }}
80
- >
81
- {Icon ? <Icon /> : null}
82
- </Flex>
83
- )}
84
- </Box>
85
- <Box padding={2}>
86
- <Text size={1} textOverflow="ellipsis" weight="medium">
64
+ <Flex align="flex-start" gap={3}>
65
+ {Icon ? (
66
+ <Text size={2}>
67
+ <Icon />
68
+ </Text>
69
+ ) : null}
70
+ <Stack flex={1} space={2}>
71
+ <Text size={1} weight="medium">
87
72
  {memberTitle(member)}
88
73
  </Text>
89
- </Box>
90
- </Stack>
74
+ {notes ? (
75
+ <Text muted size={0} style={{ lineHeight: 1.4 }}>
76
+ {notes}
77
+ </Text>
78
+ ) : null}
79
+ </Stack>
80
+ </Flex>
91
81
  </Card>
92
82
  );
93
83
  }
94
84
 
95
85
  function InsertMenuDialog({
96
86
  sections,
97
- previewImageUrl,
98
87
  onSelect,
99
88
  onClose,
100
89
  }: {
101
90
  sections: InsertMenuSection[];
102
- previewImageUrl: ((schemaTypeName: string) => string | undefined) | undefined;
103
91
  onSelect: (member: MemberSchemaType) => void;
104
92
  onClose: () => void;
105
93
  }) {
@@ -118,60 +106,108 @@ function InsertMenuDialog({
118
106
  .filter((section) => section.members.length > 0);
119
107
  }, [search, sections]);
120
108
 
109
+ const visibleMembers = visibleSections.flatMap((section) => section.members);
110
+ const [selectedName, setSelectedName] = useState(
111
+ () => visibleMembers[0]?.name,
112
+ );
113
+
114
+ const selected =
115
+ visibleMembers.find((member) => member.name === selectedName) ??
116
+ visibleMembers[0];
117
+
118
+ const selectedEntry = selected ? getLibraryEntry(selected.name) : null;
119
+
121
120
  return (
122
121
  <Dialog
122
+ footer={
123
+ <Flex gap={2} justify="flex-end" padding={3}>
124
+ <Button mode="bleed" onClick={onClose} text="Cancel" />
125
+ <Button
126
+ disabled={!selected}
127
+ onClick={() => selected && onSelect(selected)}
128
+ text="Add component"
129
+ tone="primary"
130
+ />
131
+ </Flex>
132
+ }
123
133
  header="Components"
124
134
  id="page-builder-insert"
125
135
  onClose={onClose}
126
136
  open
127
- width={3}
137
+ width={4}
128
138
  >
129
- <Stack padding={4} space={4}>
130
- <TextInput
131
- autoFocus
132
- icon={SearchIcon}
133
- onChange={(event) => setSearch(event.currentTarget.value)}
134
- placeholder="Search components…"
135
- value={search}
136
- />
137
- <Box style={{ height: SCROLL_HEIGHT, overflowY: "auto" }}>
138
- {visibleSections.length === 0 ? (
139
- <Text muted size={1}>
140
- No components match “{search}”.
141
- </Text>
139
+ <Flex gap={3} padding={3} style={{ height: DIALOG_HEIGHT }}>
140
+ <Stack
141
+ space={3}
142
+ style={{ flex: `0 0 ${CHOICE_LIST_WIDTH}px`, minWidth: 0 }}
143
+ >
144
+ <TextInput
145
+ autoFocus
146
+ icon={SearchIcon}
147
+ onChange={(event) => setSearch(event.currentTarget.value)}
148
+ placeholder="Search components…"
149
+ value={search}
150
+ />
151
+ <Box style={{ flex: 1, overflowY: "auto" }}>
152
+ {visibleSections.length === 0 ? (
153
+ <Text muted size={1}>
154
+ No components match “{search}”.
155
+ </Text>
156
+ ) : (
157
+ <Stack space={4}>
158
+ {visibleSections.map((section) => (
159
+ <Stack key={section.name} space={2}>
160
+ <Text muted size={0} weight="semibold">
161
+ {section.title.toUpperCase()}
162
+ </Text>
163
+ <Stack space={1}>
164
+ {section.members.map((member) => (
165
+ <ChoiceRow
166
+ active={member.name === selected?.name}
167
+ key={member.name}
168
+ member={member}
169
+ notes={getLibraryEntry(member.name)?.notes}
170
+ onAdd={() => onSelect(member)}
171
+ onSelect={() => setSelectedName(member.name)}
172
+ />
173
+ ))}
174
+ </Stack>
175
+ </Stack>
176
+ ))}
177
+ </Stack>
178
+ )}
179
+ </Box>
180
+ </Stack>
181
+
182
+ <Card
183
+ border
184
+ flex={1}
185
+ radius={2}
186
+ style={{ overflow: "hidden" }}
187
+ tone="transparent"
188
+ >
189
+ {selected && selectedEntry ? (
190
+ <ComponentPreviewFrame
191
+ key={selected.name}
192
+ notes={selectedEntry.notes}
193
+ path={getComponentPreviewPath(selected.name)}
194
+ />
142
195
  ) : (
143
- <Stack space={5}>
144
- {visibleSections.map((section) => (
145
- <Stack key={section.name} space={3}>
146
- <Text muted size={1} weight="semibold">
147
- {section.title}
148
- </Text>
149
- <Grid columns={[2, 3, 4]} gap={3}>
150
- {section.members.map((member) => (
151
- <BlockTile
152
- key={member.name}
153
- member={member}
154
- onSelect={() => onSelect(member)}
155
- previewUrl={previewImageUrl?.(member.name)}
156
- />
157
- ))}
158
- </Grid>
159
- </Stack>
160
- ))}
161
- </Stack>
196
+ <Flex align="center" height="fill" justify="center" padding={4}>
197
+ <Text muted size={1}>
198
+ No preview for this component yet.
199
+ </Text>
200
+ </Flex>
162
201
  )}
163
- </Box>
164
- </Stack>
202
+ </Card>
203
+ </Flex>
165
204
  </Dialog>
166
205
  );
167
206
  }
168
207
 
169
208
  /**
170
- * Pass as `arrayFunctions`. Reads `insertMenu.groups` and `insertMenu.views`, so
171
- * a schema already configuring the built-in menu needs no extra wiring.
172
- *
173
- * Replaces the built-in popover, which is anchored to the array grid element and
174
- * so clips on page scroll and resizes when switching tabs.
209
+ * Pass as `arrayFunctions`. Replaces the built-in popover, which is anchored to
210
+ * the array grid element and so clips on page scroll.
175
211
  */
176
212
  export function PageBuilderInsertMenu(
177
213
  props: ArrayInputFunctionsProps<ArrayItem, ArraySchemaType>,
@@ -199,13 +235,6 @@ export function PageBuilderInsertMenu(
199
235
  .filter((section) => section.members.length > 0);
200
236
  }, [members, insertMenu]);
201
237
 
202
- const previewImageUrl = useMemo(() => {
203
- const gridView = insertMenu?.views?.find((view) => view.name === "grid");
204
- return gridView && "previewImageUrl" in gridView
205
- ? gridView.previewImageUrl
206
- : undefined;
207
- }, [insertMenu]);
208
-
209
238
  const insert = (member: MemberSchemaType) => {
210
239
  onItemAppend((onValueCreate as unknown as CreateItem)(member));
211
240
  setOpen(false);
@@ -233,7 +262,6 @@ export function PageBuilderInsertMenu(
233
262
  <InsertMenuDialog
234
263
  onClose={() => setOpen(false)}
235
264
  onSelect={insert}
236
- previewImageUrl={previewImageUrl}
237
265
  sections={sections}
238
266
  />
239
267
  )}