bsmnt 0.11.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/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/files/components/ui/sanity-image/index.tsx +36 -4
- package/src/modules/features/cms/sanity/files/lib/integrations/sanity/components/rich-text.tsx +7 -1
- package/src/modules/features/cms/sanity-pagebuilder/files/components/sanity/rich-text.tsx +7 -1
- package/src/modules/features/cms/sanity-pagebuilder/files/components/ui/sanity-image/index.tsx +36 -4
- 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/page-builder-input.tsx +26 -7
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/page-builder-insert-menu.tsx +242 -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/components/use-studio-preview-urls.ts +62 -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 +72 -34
- 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/index.ts +1 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/reusable/blog-content.ts +3 -3
- 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/components/studio-preview.ts +37 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/index.ts +11 -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 +9 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/structure.ts +5 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/components/sanity/rich-text.tsx +7 -1
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/components/ui/sanity-image/index.tsx +36 -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/page-builder-input.tsx +26 -7
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/page-builder-insert-menu.tsx +242 -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/components/use-studio-preview-urls.ts +62 -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 +72 -34
- 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/index.ts +1 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/reusable/blog-content.ts +3 -3
- 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/components/studio-preview.ts +37 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/index.ts +11 -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 +9 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/structure.ts +5 -0
- 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/.biome/plugins/no-fill-without-sizes.grit +11 -0
- package/src/templates/next-default/biome.json +2 -1
- package/src/templates/next-default/components/ui/image/index.tsx +20 -12
- package/src/templates/next-default/next.config.ts +10 -4
- package/src/templates/next-experiments/.biome/plugins/no-fill-without-sizes.grit +11 -0
- package/src/templates/next-experiments/biome.json +2 -1
- package/src/templates/next-experiments/components/ui/image/index.tsx +20 -12
- package/src/templates/next-experiments/next.config.ts +10 -4
- package/src/templates/next-pagebuilder/.biome/plugins/no-fill-without-sizes.grit +11 -0
- 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/biome.json +2 -1
- 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 +6 -1
- package/src/templates/next-pagebuilder/components/page-builder/components/article-content/related-post-item.tsx +1 -0
- 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 +1 -1
- package/src/templates/next-pagebuilder/components/ui/image/index.tsx +20 -12
- package/src/templates/next-pagebuilder/lib/integrations/sanity/sanity.types.ts +92 -4
- package/src/templates/next-pagebuilder/next.config.ts +4 -3
- package/src/templates/next-pagebuilder-cache/.biome/plugins/no-fill-without-sizes.grit +11 -0
- 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/biome.json +2 -1
- 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 +6 -1
- package/src/templates/next-pagebuilder-cache/components/page-builder/components/article-content/related-post-item.tsx +1 -0
- 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 +1 -1
- package/src/templates/next-pagebuilder-cache/components/ui/image/index.tsx +21 -12
- 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 +72 -0
- package/src/templates/next-pagebuilder-cache/next.config.ts +4 -3
- package/src/templates/next-webgl/.biome/plugins/no-fill-without-sizes.grit +11 -0
- package/src/templates/next-webgl/biome.json +2 -1
- package/src/templates/next-webgl/components/ui/image/index.tsx +20 -12
- package/src/templates/next-webgl/next.config.ts +10 -4
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: page-builder-block
|
|
3
|
+
description: >-
|
|
4
|
+
Create or modify a Sanity page-builder block in a basement page-builder
|
|
5
|
+
project so it follows the repo conventions: defined with `defineBlock`, drawn
|
|
6
|
+
through the renderer's block registry, Anchor ID (generated from the title
|
|
7
|
+
when empty), Presentation Mode, and spacing owned by the frame that instances
|
|
8
|
+
the block rather than the block itself. Use when asked to "add a block to the
|
|
9
|
+
page builder", "create a new section/component for Sanity", "add anchor links
|
|
10
|
+
to a block", "control the spacing of a block from Sanity", or when reviewing
|
|
11
|
+
an existing block for these rules.
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# Page-builder block conventions
|
|
15
|
+
|
|
16
|
+
Blocks are **documents** referenced from the `pageBuilder` array, drawn by
|
|
17
|
+
`components/page-builder/renderer.tsx` through the registry in
|
|
18
|
+
`components/page-builder/registry.tsx`. Read one existing block end to end
|
|
19
|
+
before writing a new one — `hero` is the smallest complete example:
|
|
20
|
+
|
|
21
|
+
- schema: `lib/integrations/sanity/schemas/components/reusable/hero.ts`
|
|
22
|
+
- component: `components/page-builder/components/hero.tsx`
|
|
23
|
+
- registration: `lib/integrations/sanity/schemas/components/index.ts`,
|
|
24
|
+
`lib/integrations/sanity/schemas/index.ts`,
|
|
25
|
+
`lib/integrations/sanity/page-builder-config.ts`,
|
|
26
|
+
`lib/integrations/sanity/structure.ts`,
|
|
27
|
+
`components/page-builder/registry.tsx`
|
|
28
|
+
|
|
29
|
+
A block exists in two halves — a schema and a component — and each half has one
|
|
30
|
+
place that makes it a block rather than an ordinary document or a loose React
|
|
31
|
+
component. Use them and the conventions below come for free; skip them and a
|
|
32
|
+
typecheck fails.
|
|
33
|
+
|
|
34
|
+
## Non-negotiables
|
|
35
|
+
|
|
36
|
+
1. **Define the schema with `defineBlock`, never `defineType`.**
|
|
37
|
+
`lib/integrations/sanity/schemas/shared/block.ts` implies
|
|
38
|
+
`type: "document"` and appends Anchor ID + Presentation Mode as the last two
|
|
39
|
+
fields:
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
import { defineField } from "sanity"
|
|
43
|
+
import { defineBlock } from "@/lib/integrations/sanity/schemas/shared/block"
|
|
44
|
+
|
|
45
|
+
export const featureGrid = defineBlock({
|
|
46
|
+
name: "featureGrid",
|
|
47
|
+
title: "Feature Grid",
|
|
48
|
+
icon: featureGridIcon,
|
|
49
|
+
fields: [
|
|
50
|
+
// ...content fields...
|
|
51
|
+
],
|
|
52
|
+
preview: { /* ... */ },
|
|
53
|
+
})
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
What it adds:
|
|
57
|
+
- `anchorId` — optional. Left empty, the anchor is **generated from the
|
|
58
|
+
block's title** at render time (`resolveAnchorId` in
|
|
59
|
+
`lib/integrations/sanity/schemas/shared/anchor.ts` walks `headline`,
|
|
60
|
+
`heading`, `teaserHeading`, `title`, `label` — never `internalTitle`, which
|
|
61
|
+
is Studio-only) and de-duplicated per page. A value typed by hand always
|
|
62
|
+
wins. Name your block's headline field one of those and it works for free.
|
|
63
|
+
- `presentation` — object with `paddingTop` / `paddingBottom`, each storing
|
|
64
|
+
the `_key` of a step from the shared scale. No manual px, ever. A block
|
|
65
|
+
that never touches it follows whichever step is marked as the default.
|
|
66
|
+
|
|
67
|
+
Blocks that declare `groups` get a **Presentation** tab for the two fields,
|
|
68
|
+
added automatically — don't list it in `groups` yourself. A block with no
|
|
69
|
+
groups shows them inline, at the bottom of the form.
|
|
70
|
+
|
|
71
|
+
2. **Register the component in the block registry.** One entry per block, keyed
|
|
72
|
+
by schema type; the renderer has no switch to extend:
|
|
73
|
+
|
|
74
|
+
```tsx
|
|
75
|
+
featureGrid: {
|
|
76
|
+
render: (block) => <FeatureGrid block={block} />,
|
|
77
|
+
isEmpty: (block) => !block.items?.length,
|
|
78
|
+
},
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
- `render` receives the block narrowed to its own `_type`, plus a
|
|
82
|
+
`BlockContext` for page-level values a block doesn't own (`searchParams`,
|
|
83
|
+
`pinnedCategory`). Take the second argument only if you need it.
|
|
84
|
+
- `isEmpty` is optional and declares when the block has nothing to show. The
|
|
85
|
+
renderer then skips the block **and** its `<Section>`, so an unfilled block
|
|
86
|
+
leaves no padded gap behind and doesn't claim an anchor id. Don't
|
|
87
|
+
early-return `null` from the component instead — the renderer has to know
|
|
88
|
+
before it wraps anything.
|
|
89
|
+
|
|
90
|
+
The registry's key set is typed as exactly `PageBuilderDocumentType`, so a
|
|
91
|
+
block registered in `page-builder-config.ts` without an entry here — or an
|
|
92
|
+
entry here for a block that was never registered there — is a typecheck
|
|
93
|
+
error.
|
|
94
|
+
|
|
95
|
+
3. **Never hardcode vertical padding inside a block component.** The renderer
|
|
96
|
+
wraps every block in `<Section>` (`components/page-builder/section.tsx`),
|
|
97
|
+
which renders the `<section>` element, applies the anchor `id` and the
|
|
98
|
+
padding. A block component returns its content only — no outer `<section>`,
|
|
99
|
+
no `py-*` / `pt-*` / `pb-*` on its root. Internal gaps between a block's own
|
|
100
|
+
parts are fine (`gap-*`, `space-y-*`, `mt-*` on children).
|
|
101
|
+
|
|
102
|
+
4. **The spacing scale is content, not code.** Editors own it at **Layout →
|
|
103
|
+
Spacing Scale** (`spacingSettings`): a list of steps, each with a name, px
|
|
104
|
+
per breakpoint (mobile / tablet / desktop, empty reuses Desktop) and one
|
|
105
|
+
marked as the default — ticking a second one unticks the previous, so there
|
|
106
|
+
is always exactly one. They add, rename or remove steps without touching
|
|
107
|
+
code; when the list is empty the field offers an "Add the built-in steps"
|
|
108
|
+
button that seeds it.
|
|
109
|
+
`lib/integrations/sanity/schemas/shared/spacing.ts` holds the five built-in
|
|
110
|
+
steps (XS · SM · MD · LG · XL, MD default) used to seed the document and as
|
|
111
|
+
the fallback when it is empty, plus the resolver and the CSS builder.
|
|
112
|
+
|
|
113
|
+
Resolved values ship as CSS custom properties from
|
|
114
|
+
`components/layout/spacing-scale/index.tsx` (rendered by
|
|
115
|
+
`app/(content)/layout.tsx`), one `--section-space-<step key>` per step plus
|
|
116
|
+
`--section-space-default`, re-declared inside `min-width: 768px` and
|
|
117
|
+
`1440px` media queries. `Section` only references
|
|
118
|
+
`var(--section-space-<key>, var(--section-space-default, 2.5rem))`, so a
|
|
119
|
+
deleted or unknown step degrades to the default instead of breaking, and
|
|
120
|
+
**changing a value never requires a deploy**.
|
|
121
|
+
|
|
122
|
+
Publishing `spacingSettings` busts the layout cache: it is in
|
|
123
|
+
`SANITY_LAYOUT_TAGS` (`fetchers/layout.ts`, or `tags.ts` on the Cache
|
|
124
|
+
Components variant), same as navbar and footer.
|
|
125
|
+
|
|
126
|
+
5. **Regenerate types after a schema change:** `bun run sanity:typegen`. The
|
|
127
|
+
page query spreads `...` for non-gated blocks, so new fields flow through
|
|
128
|
+
automatically — except `gatedContent`, whose GROQ branch lists fields
|
|
129
|
+
explicitly (`lib/integrations/sanity/queries.ts`). Add new fields there by
|
|
130
|
+
hand if the block is gated.
|
|
131
|
+
|
|
132
|
+
## Checklist for a new block
|
|
133
|
+
|
|
134
|
+
- [ ] Schema in `schemas/components/reusable/<block>.ts` (or `singleton/` for a
|
|
135
|
+
one-instance block), built with `defineBlock` and carrying a `preview`.
|
|
136
|
+
- [ ] Icon in `lib/integrations/sanity/icons.ts` (phosphor duotone).
|
|
137
|
+
- [ ] Exported from `schemas/components/index.ts` and registered in
|
|
138
|
+
`schemas/index.ts` (`schema.types`).
|
|
139
|
+
- [ ] Member added to `page-builder-config.ts`: the `PageBuilderDocumentType`
|
|
140
|
+
union plus an entry in `reusablePageBuilderReferenceMembers` carrying
|
|
141
|
+
**both** axes — `pageTypes` (which page documents may host it) and
|
|
142
|
+
`category` (what the block is, one of `componentCategories`; `misc` only
|
|
143
|
+
when nothing fits).
|
|
144
|
+
- [ ] Listed in `structure.ts` under the right folder.
|
|
145
|
+
- [ ] Component in `components/page-builder/components/<block>.tsx`, typed with
|
|
146
|
+
`Extract<PageBuilderBlock, { _type: "<block>" }>`, no outer section, no
|
|
147
|
+
vertical padding. Variants via `cva`/`cn` when the block has variants.
|
|
148
|
+
- [ ] Entry in `components/page-builder/registry.tsx`, with `isEmpty` when the
|
|
149
|
+
block can be left unfilled.
|
|
150
|
+
- [ ] `bun run sanity:typegen && bun run typecheck && bun run lint && bun run build && bun run knip`.
|
|
@@ -2,7 +2,8 @@ import { getImageDimensions } from "@sanity/asset-utils";
|
|
|
2
2
|
import { Image, type ImageProps } from "@/components/ui/image";
|
|
3
3
|
import { urlForImage } from "@/lib/integrations/sanity/utils/image";
|
|
4
4
|
|
|
5
|
-
interface
|
|
5
|
+
interface SanityImageBaseProps
|
|
6
|
+
extends Omit<ImageProps, "src" | "aspectRatio" | "sizes" | "width" | "height"> {
|
|
6
7
|
image: {
|
|
7
8
|
asset?: {
|
|
8
9
|
_ref: string;
|
|
@@ -15,6 +16,38 @@ interface SanityImageProps extends Omit<ImageProps, "src" | "aspectRatio"> {
|
|
|
15
16
|
maxWidth?: number;
|
|
16
17
|
}
|
|
17
18
|
|
|
19
|
+
type SanityImageProps = SanityImageBaseProps &
|
|
20
|
+
(
|
|
21
|
+
| { sizes: string; width?: never; height?: never }
|
|
22
|
+
| { width: number; height: number; sizes?: string }
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
const MAX_SOURCE_WIDTH = 3840;
|
|
26
|
+
|
|
27
|
+
const isGifRef = (ref: string) => ref.endsWith("-gif");
|
|
28
|
+
|
|
29
|
+
const getImageSrc = (
|
|
30
|
+
image: SanityImageProps["image"],
|
|
31
|
+
maxWidth: number,
|
|
32
|
+
croppedWidth: number,
|
|
33
|
+
) => {
|
|
34
|
+
const builder = urlForImage(image);
|
|
35
|
+
const ref = image.asset?._ref ?? "";
|
|
36
|
+
|
|
37
|
+
if (ref.endsWith("-svg")) return builder.url();
|
|
38
|
+
|
|
39
|
+
const requestWidth = Math.min(maxWidth, MAX_SOURCE_WIDTH, croppedWidth);
|
|
40
|
+
const { left, right, top, bottom } = image.crop ?? {};
|
|
41
|
+
const hasCrop = Boolean(left || right || top || bottom);
|
|
42
|
+
const isGif = isGifRef(ref);
|
|
43
|
+
|
|
44
|
+
if (!(isGif || hasCrop) && requestWidth >= croppedWidth) {
|
|
45
|
+
return builder.url();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return builder.width(requestWidth).url();
|
|
49
|
+
};
|
|
50
|
+
|
|
18
51
|
export function SanityImage({
|
|
19
52
|
image,
|
|
20
53
|
maxWidth = 1920,
|
|
@@ -39,11 +72,10 @@ export function SanityImage({
|
|
|
39
72
|
|
|
40
73
|
return (
|
|
41
74
|
<Image
|
|
42
|
-
src={
|
|
43
|
-
.width(Math.min(maxWidth, sourceWidth))
|
|
44
|
-
.url()}
|
|
75
|
+
src={getImageSrc(image, maxWidth, sourceWidth)}
|
|
45
76
|
alt={alt || image.alt || ""}
|
|
46
77
|
aspectRatio={sourceWidth / sourceHeight}
|
|
78
|
+
unoptimized={isGifRef(image.asset._ref)}
|
|
47
79
|
{...imageProps}
|
|
48
80
|
{...props}
|
|
49
81
|
/>
|
package/src/modules/features/cms/sanity/files/lib/integrations/sanity/components/rich-text.tsx
CHANGED
|
@@ -37,7 +37,13 @@ export function RichText({ content }: RichTextProps) {
|
|
|
37
37
|
value={content}
|
|
38
38
|
components={{
|
|
39
39
|
types: {
|
|
40
|
-
image: ({ value }) =>
|
|
40
|
+
image: ({ value }) => (
|
|
41
|
+
<SanityImage
|
|
42
|
+
image={value}
|
|
43
|
+
maxWidth={1920}
|
|
44
|
+
sizes="(min-width: 768px) 768px, 100vw"
|
|
45
|
+
/>
|
|
46
|
+
),
|
|
41
47
|
},
|
|
42
48
|
marks: {
|
|
43
49
|
link: ({ children, value }) => {
|
|
@@ -42,7 +42,13 @@ export const RichText = ({ content }: RichTextProps) => {
|
|
|
42
42
|
value={content}
|
|
43
43
|
components={{
|
|
44
44
|
types: {
|
|
45
|
-
image: ({ value }) =>
|
|
45
|
+
image: ({ value }) => (
|
|
46
|
+
<SanityImage
|
|
47
|
+
image={value}
|
|
48
|
+
maxWidth={1920}
|
|
49
|
+
sizes="(min-width: 768px) 768px, 100vw"
|
|
50
|
+
/>
|
|
51
|
+
),
|
|
46
52
|
table: ({ value }) => {
|
|
47
53
|
const rows = value?.rows as
|
|
48
54
|
| Array<{ _key?: string; cells?: string[] }>
|
package/src/modules/features/cms/sanity-pagebuilder/files/components/ui/sanity-image/index.tsx
CHANGED
|
@@ -2,7 +2,8 @@ import { getImageDimensions } from "@sanity/asset-utils";
|
|
|
2
2
|
import { Image, type ImageProps } from "@/components/ui/image";
|
|
3
3
|
import { urlForImage } from "@/lib/integrations/sanity/utils/image";
|
|
4
4
|
|
|
5
|
-
interface
|
|
5
|
+
interface SanityImageBaseProps
|
|
6
|
+
extends Omit<ImageProps, "src" | "aspectRatio" | "sizes" | "width" | "height"> {
|
|
6
7
|
image: {
|
|
7
8
|
asset?: {
|
|
8
9
|
_ref: string;
|
|
@@ -15,6 +16,38 @@ interface SanityImageProps extends Omit<ImageProps, "src" | "aspectRatio"> {
|
|
|
15
16
|
maxWidth?: number;
|
|
16
17
|
}
|
|
17
18
|
|
|
19
|
+
type SanityImageProps = SanityImageBaseProps &
|
|
20
|
+
(
|
|
21
|
+
| { sizes: string; width?: never; height?: never }
|
|
22
|
+
| { width: number; height: number; sizes?: string }
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
const MAX_SOURCE_WIDTH = 3840;
|
|
26
|
+
|
|
27
|
+
const isGifRef = (ref: string) => ref.endsWith("-gif");
|
|
28
|
+
|
|
29
|
+
const getImageSrc = (
|
|
30
|
+
image: SanityImageProps["image"],
|
|
31
|
+
maxWidth: number,
|
|
32
|
+
croppedWidth: number,
|
|
33
|
+
) => {
|
|
34
|
+
const builder = urlForImage(image);
|
|
35
|
+
const ref = image.asset?._ref ?? "";
|
|
36
|
+
|
|
37
|
+
if (ref.endsWith("-svg")) return builder.url();
|
|
38
|
+
|
|
39
|
+
const requestWidth = Math.min(maxWidth, MAX_SOURCE_WIDTH, croppedWidth);
|
|
40
|
+
const { left, right, top, bottom } = image.crop ?? {};
|
|
41
|
+
const hasCrop = Boolean(left || right || top || bottom);
|
|
42
|
+
const isGif = isGifRef(ref);
|
|
43
|
+
|
|
44
|
+
if (!(isGif || hasCrop) && requestWidth >= croppedWidth) {
|
|
45
|
+
return builder.url();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return builder.width(requestWidth).url();
|
|
49
|
+
};
|
|
50
|
+
|
|
18
51
|
export function SanityImage({
|
|
19
52
|
image,
|
|
20
53
|
maxWidth = 1920,
|
|
@@ -39,11 +72,10 @@ export function SanityImage({
|
|
|
39
72
|
|
|
40
73
|
return (
|
|
41
74
|
<Image
|
|
42
|
-
src={
|
|
43
|
-
.width(Math.min(maxWidth, sourceWidth))
|
|
44
|
-
.url()}
|
|
75
|
+
src={getImageSrc(image, maxWidth, sourceWidth)}
|
|
45
76
|
alt={alt || image.alt || ""}
|
|
46
77
|
aspectRatio={sourceWidth / sourceHeight}
|
|
78
|
+
unoptimized={isGifRef(image.asset._ref)}
|
|
47
79
|
{...imageProps}
|
|
48
80
|
{...props}
|
|
49
81
|
/>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type StringInputProps, useFormValue } from "sanity";
|
|
2
|
+
import { deriveAnchorId } from "@/lib/integrations/sanity/schemas/shared/anchor";
|
|
3
|
+
|
|
4
|
+
export function AnchorIdInput(props: StringInputProps) {
|
|
5
|
+
const document = useFormValue([]) as Record<string, unknown> | undefined;
|
|
6
|
+
const derived = deriveAnchorId(document ?? {});
|
|
7
|
+
|
|
8
|
+
return props.renderDefault({
|
|
9
|
+
...props,
|
|
10
|
+
elementProps: {
|
|
11
|
+
...props.elementProps,
|
|
12
|
+
placeholder: derived
|
|
13
|
+
? `${derived} — generated from the title`
|
|
14
|
+
: "generated from the title",
|
|
15
|
+
},
|
|
16
|
+
});
|
|
17
|
+
}
|
|
@@ -1,25 +1,43 @@
|
|
|
1
1
|
import { type ArrayOfObjectsInputProps, useFormValue } from "sanity";
|
|
2
|
+
import { PageBuilderInsertMenu } from "@/lib/integrations/sanity/components/page-builder-insert-menu";
|
|
3
|
+
import { useStudioPreviewUrls } from "@/lib/integrations/sanity/components/use-studio-preview-urls";
|
|
2
4
|
import {
|
|
5
|
+
blockPreviewKey,
|
|
3
6
|
getVisiblePageBuilderInsertMenuGroups,
|
|
4
|
-
|
|
7
|
+
getVisiblePageBuilderMemberNames,
|
|
5
8
|
type PageBuilderGroupName,
|
|
9
|
+
pageBuilderReferenceMembers,
|
|
6
10
|
} from "@/lib/integrations/sanity/page-builder-config";
|
|
7
11
|
|
|
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
|
+
|
|
8
29
|
export function PageBuilderInput(props: ArrayOfObjectsInputProps) {
|
|
9
30
|
const pageTypeValue = useFormValue(["type"]);
|
|
10
31
|
const pageType =
|
|
11
32
|
typeof pageTypeValue === "string"
|
|
12
33
|
? (pageTypeValue as PageBuilderGroupName)
|
|
13
34
|
: undefined;
|
|
14
|
-
|
|
15
|
-
const visibleMemberNames =
|
|
16
|
-
getVisiblePageBuilderReferenceMembers(pageType).map(
|
|
17
|
-
(member) => member.name,
|
|
18
|
-
),
|
|
19
|
-
);
|
|
35
|
+
const views = useInsertMenuViews();
|
|
36
|
+
const visibleMemberNames = getVisiblePageBuilderMemberNames(pageType);
|
|
20
37
|
|
|
21
38
|
return props.renderDefault({
|
|
22
39
|
...props,
|
|
40
|
+
arrayFunctions: PageBuilderInsertMenu,
|
|
23
41
|
schemaType: {
|
|
24
42
|
...props.schemaType,
|
|
25
43
|
of: props.schemaType.of.filter(
|
|
@@ -32,6 +50,7 @@ export function PageBuilderInput(props: ArrayOfObjectsInputProps) {
|
|
|
32
50
|
insertMenu: {
|
|
33
51
|
...props.schemaType.options?.insertMenu,
|
|
34
52
|
groups: getVisiblePageBuilderInsertMenuGroups(pageType),
|
|
53
|
+
views,
|
|
35
54
|
},
|
|
36
55
|
},
|
|
37
56
|
},
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { AddIcon, SearchIcon } from "@sanity/icons";
|
|
2
|
+
import {
|
|
3
|
+
Box,
|
|
4
|
+
Button,
|
|
5
|
+
Card,
|
|
6
|
+
Dialog,
|
|
7
|
+
Flex,
|
|
8
|
+
Grid,
|
|
9
|
+
Stack,
|
|
10
|
+
Text,
|
|
11
|
+
TextInput,
|
|
12
|
+
} from "@sanity/ui";
|
|
13
|
+
import { useMemo, useState } from "react";
|
|
14
|
+
import type { ArrayInputFunctionsProps, ArraySchemaType } from "sanity";
|
|
15
|
+
|
|
16
|
+
type ArrayItem = { _key: string };
|
|
17
|
+
type MemberSchemaType = ArraySchemaType["of"][number];
|
|
18
|
+
|
|
19
|
+
type CreateItem = (memberType: MemberSchemaType) => ArrayItem;
|
|
20
|
+
|
|
21
|
+
type InsertMenuSection = {
|
|
22
|
+
name: string;
|
|
23
|
+
title: string;
|
|
24
|
+
members: MemberSchemaType[];
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const SCROLL_HEIGHT = "min(60vh, 34rem)";
|
|
28
|
+
const TILE_ASPECT_RATIO = "3 / 2";
|
|
29
|
+
|
|
30
|
+
const memberTitle = (member: MemberSchemaType) => member.title ?? member.name;
|
|
31
|
+
|
|
32
|
+
function BlockTile({
|
|
33
|
+
member,
|
|
34
|
+
previewUrl,
|
|
35
|
+
onSelect,
|
|
36
|
+
}: {
|
|
37
|
+
member: MemberSchemaType;
|
|
38
|
+
previewUrl: string | undefined;
|
|
39
|
+
onSelect: () => void;
|
|
40
|
+
}) {
|
|
41
|
+
const Icon = member.icon;
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<Card
|
|
45
|
+
as="button"
|
|
46
|
+
border
|
|
47
|
+
onClick={onSelect}
|
|
48
|
+
overflow="hidden"
|
|
49
|
+
padding={0}
|
|
50
|
+
radius={2}
|
|
51
|
+
style={{ cursor: "pointer", textAlign: "left", width: "100%" }}
|
|
52
|
+
type="button"
|
|
53
|
+
>
|
|
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">
|
|
87
|
+
{memberTitle(member)}
|
|
88
|
+
</Text>
|
|
89
|
+
</Box>
|
|
90
|
+
</Stack>
|
|
91
|
+
</Card>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function InsertMenuDialog({
|
|
96
|
+
sections,
|
|
97
|
+
previewImageUrl,
|
|
98
|
+
onSelect,
|
|
99
|
+
onClose,
|
|
100
|
+
}: {
|
|
101
|
+
sections: InsertMenuSection[];
|
|
102
|
+
previewImageUrl: ((schemaTypeName: string) => string | undefined) | undefined;
|
|
103
|
+
onSelect: (member: MemberSchemaType) => void;
|
|
104
|
+
onClose: () => void;
|
|
105
|
+
}) {
|
|
106
|
+
const [search, setSearch] = useState("");
|
|
107
|
+
|
|
108
|
+
const visibleSections = useMemo(() => {
|
|
109
|
+
const query = search.trim().toLowerCase();
|
|
110
|
+
if (!query) return sections;
|
|
111
|
+
return sections
|
|
112
|
+
.map((section) => ({
|
|
113
|
+
...section,
|
|
114
|
+
members: section.members.filter((member) =>
|
|
115
|
+
memberTitle(member).toLowerCase().includes(query),
|
|
116
|
+
),
|
|
117
|
+
}))
|
|
118
|
+
.filter((section) => section.members.length > 0);
|
|
119
|
+
}, [search, sections]);
|
|
120
|
+
|
|
121
|
+
return (
|
|
122
|
+
<Dialog
|
|
123
|
+
header="Components"
|
|
124
|
+
id="page-builder-insert"
|
|
125
|
+
onClose={onClose}
|
|
126
|
+
open
|
|
127
|
+
width={3}
|
|
128
|
+
>
|
|
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>
|
|
142
|
+
) : (
|
|
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>
|
|
162
|
+
)}
|
|
163
|
+
</Box>
|
|
164
|
+
</Stack>
|
|
165
|
+
</Dialog>
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
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.
|
|
175
|
+
*/
|
|
176
|
+
export function PageBuilderInsertMenu(
|
|
177
|
+
props: ArrayInputFunctionsProps<ArrayItem, ArraySchemaType>,
|
|
178
|
+
) {
|
|
179
|
+
const { onItemAppend, onValueCreate, readOnly, schemaType } = props;
|
|
180
|
+
const [open, setOpen] = useState(false);
|
|
181
|
+
|
|
182
|
+
const insertMenu = schemaType.options?.insertMenu;
|
|
183
|
+
const members = schemaType.of;
|
|
184
|
+
|
|
185
|
+
const sections = useMemo<InsertMenuSection[]>(() => {
|
|
186
|
+
const byName = new Map(members.map((member) => [member.name, member]));
|
|
187
|
+
const groups = insertMenu?.groups;
|
|
188
|
+
if (!groups) {
|
|
189
|
+
return [{ name: "all", title: "Components", members: [...members] }];
|
|
190
|
+
}
|
|
191
|
+
return groups
|
|
192
|
+
.map((group) => ({
|
|
193
|
+
name: group.name,
|
|
194
|
+
title: group.title ?? group.name,
|
|
195
|
+
members: (group.of ?? [])
|
|
196
|
+
.map((name) => byName.get(name))
|
|
197
|
+
.filter((member) => member !== undefined),
|
|
198
|
+
}))
|
|
199
|
+
.filter((section) => section.members.length > 0);
|
|
200
|
+
}, [members, insertMenu]);
|
|
201
|
+
|
|
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
|
+
const insert = (member: MemberSchemaType) => {
|
|
210
|
+
onItemAppend((onValueCreate as unknown as CreateItem)(member));
|
|
211
|
+
setOpen(false);
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
const onlyMember = members.length === 1 ? members[0] : undefined;
|
|
215
|
+
|
|
216
|
+
if (schemaType.options?.disableActions?.includes("add")) return null;
|
|
217
|
+
|
|
218
|
+
return (
|
|
219
|
+
<>
|
|
220
|
+
<Grid>
|
|
221
|
+
<Button
|
|
222
|
+
disabled={readOnly === true}
|
|
223
|
+
fontSize={1}
|
|
224
|
+
icon={AddIcon}
|
|
225
|
+
mode="ghost"
|
|
226
|
+
onClick={() => (onlyMember ? insert(onlyMember) : setOpen(true))}
|
|
227
|
+
padding={3}
|
|
228
|
+
text={onlyMember ? "Add item" : "Add item…"}
|
|
229
|
+
/>
|
|
230
|
+
</Grid>
|
|
231
|
+
|
|
232
|
+
{open && (
|
|
233
|
+
<InsertMenuDialog
|
|
234
|
+
onClose={() => setOpen(false)}
|
|
235
|
+
onSelect={insert}
|
|
236
|
+
previewImageUrl={previewImageUrl}
|
|
237
|
+
sections={sections}
|
|
238
|
+
/>
|
|
239
|
+
)}
|
|
240
|
+
</>
|
|
241
|
+
);
|
|
242
|
+
}
|