@morphika/andami 0.5.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +151 -36
- package/app/admin/layout.tsx +145 -152
- package/components/blocks/AudioBlockRenderer.tsx +286 -0
- package/components/blocks/BeforeAfterBlockRenderer.tsx +274 -0
- package/components/builder/BlockCardIcons.tsx +89 -0
- package/components/builder/BlockTypePicker.tsx +2 -0
- package/components/builder/CoverSectionCanvas.tsx +90 -2
- package/components/builder/SectionV2Canvas.tsx +19 -3
- package/components/builder/SectionV2Column.tsx +5 -1
- package/components/builder/asset-browser/R2BrowserContent.tsx +23 -6
- package/components/builder/asset-browser/helpers.ts +4 -0
- package/components/builder/asset-browser/types.ts +2 -1
- package/components/builder/blockStyles.tsx +12 -0
- package/components/builder/editors/AudioBlockEditor.tsx +242 -0
- package/components/builder/editors/BeforeAfterBlockEditor.tsx +360 -0
- package/components/builder/editors/shared.tsx +1 -1
- package/components/builder/live-preview/LiveAudioPreview.tsx +120 -0
- package/components/builder/live-preview/LiveBeforeAfterPreview.tsx +176 -0
- package/lib/animation/enter-types.ts +2 -0
- package/lib/animation/hover-effect-types.ts +2 -0
- package/lib/builder/block-registrations.ts +83 -1
- package/lib/builder/types.ts +2 -0
- package/lib/sanity/types.ts +58 -0
- package/lib/version.ts +1 -1
- package/package.json +1 -1
- package/sanity/schemas/blocks/audioBlock.ts +69 -0
- package/sanity/schemas/blocks/beforeAfterBlock.ts +121 -0
- package/sanity/schemas/blocks/index.ts +3 -1
- package/sanity/schemas/index.ts +7 -1
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Block registrations — fills the registry defined in `./block-registry.ts`
|
|
5
|
-
* with the
|
|
5
|
+
* with the 10 built-in block types shipped by the framework.
|
|
6
6
|
*
|
|
7
7
|
* Importing this module has a side effect: every `registerBlockType()`
|
|
8
8
|
* call below runs synchronously and populates the module-level registry.
|
|
@@ -26,6 +26,8 @@ import type {
|
|
|
26
26
|
VideoBlock,
|
|
27
27
|
SpacerBlock,
|
|
28
28
|
ButtonBlock,
|
|
29
|
+
BeforeAfterBlock,
|
|
30
|
+
AudioBlock,
|
|
29
31
|
ProjectGridBlock,
|
|
30
32
|
ProjectCarouselBlock,
|
|
31
33
|
} from "../sanity/types";
|
|
@@ -39,6 +41,8 @@ import {
|
|
|
39
41
|
videoBlock,
|
|
40
42
|
spacerBlock,
|
|
41
43
|
buttonBlock,
|
|
44
|
+
beforeAfterBlock,
|
|
45
|
+
audioBlock,
|
|
42
46
|
projectGridBlock,
|
|
43
47
|
projectCarouselBlock,
|
|
44
48
|
} from "../../sanity/schemas/blocks";
|
|
@@ -51,6 +55,8 @@ import ImageGridBlockRenderer from "../../components/blocks/ImageGridBlockRender
|
|
|
51
55
|
import VideoBlockRenderer from "../../components/blocks/VideoBlockRenderer";
|
|
52
56
|
import SpacerBlockRenderer from "../../components/blocks/SpacerBlockRenderer";
|
|
53
57
|
import ButtonBlockRenderer from "../../components/blocks/ButtonBlockRenderer";
|
|
58
|
+
import BeforeAfterBlockRenderer from "../../components/blocks/BeforeAfterBlockRenderer";
|
|
59
|
+
import AudioBlockRenderer from "../../components/blocks/AudioBlockRenderer";
|
|
54
60
|
import ProjectGridBlockRenderer from "../../components/blocks/ProjectGridBlockRenderer";
|
|
55
61
|
import ProjectCarouselBlockRenderer from "../../components/blocks/ProjectCarouselBlockRenderer";
|
|
56
62
|
|
|
@@ -62,6 +68,8 @@ import LiveImageGridPreview from "../../components/builder/live-preview/LiveImag
|
|
|
62
68
|
import LiveVideoPreview from "../../components/builder/live-preview/LiveVideoPreview";
|
|
63
69
|
import LiveSpacerPreview from "../../components/builder/live-preview/LiveSpacerPreview";
|
|
64
70
|
import LiveButtonPreview from "../../components/builder/live-preview/LiveButtonPreview";
|
|
71
|
+
import LiveBeforeAfterPreview from "../../components/builder/live-preview/LiveBeforeAfterPreview";
|
|
72
|
+
import LiveAudioPreview from "../../components/builder/live-preview/LiveAudioPreview";
|
|
65
73
|
import LiveProjectGridPreview from "../../components/builder/live-preview/LiveProjectGridPreview";
|
|
66
74
|
import LiveProjectCarouselPreview from "../../components/builder/live-preview/LiveProjectCarouselPreview";
|
|
67
75
|
|
|
@@ -73,6 +81,8 @@ import ImageGridBlockEditor from "../../components/builder/editors/ImageGridBloc
|
|
|
73
81
|
import VideoBlockEditor from "../../components/builder/editors/VideoBlockEditor";
|
|
74
82
|
import SpacerBlockEditor from "../../components/builder/editors/SpacerBlockEditor";
|
|
75
83
|
import ButtonBlockEditor from "../../components/builder/editors/ButtonBlockEditor";
|
|
84
|
+
import BeforeAfterBlockEditor from "../../components/builder/editors/BeforeAfterBlockEditor";
|
|
85
|
+
import AudioBlockEditor from "../../components/builder/editors/AudioBlockEditor";
|
|
76
86
|
import ProjectGridEditor from "../../components/builder/editors/ProjectGridEditor";
|
|
77
87
|
import ProjectCarouselBlockEditor from "../../components/builder/editors/ProjectCarouselBlockEditor";
|
|
78
88
|
|
|
@@ -85,6 +95,8 @@ import {
|
|
|
85
95
|
VideoBlockCardIcon,
|
|
86
96
|
SpacerBlockCardIcon,
|
|
87
97
|
ButtonBlockCardIcon,
|
|
98
|
+
BeforeAfterBlockCardIcon,
|
|
99
|
+
AudioBlockCardIcon,
|
|
88
100
|
} from "../../components/builder/BlockCardIcons";
|
|
89
101
|
import {
|
|
90
102
|
ProjectGridCardIcon,
|
|
@@ -100,6 +112,8 @@ import {
|
|
|
100
112
|
VideoBlockIcon,
|
|
101
113
|
SpacerBlockIcon,
|
|
102
114
|
ButtonBlockIcon,
|
|
115
|
+
BeforeAfterBlockIcon,
|
|
116
|
+
AudioBlockIcon,
|
|
103
117
|
ProjectGridBlockIcon,
|
|
104
118
|
ProjectCarouselBlockIcon,
|
|
105
119
|
} from "../../components/builder/blockStyles";
|
|
@@ -267,6 +281,74 @@ registerBlockType<ButtonBlock>({
|
|
|
267
281
|
hoverPresets: ["scale-up", "lift", "border-glow"],
|
|
268
282
|
});
|
|
269
283
|
|
|
284
|
+
registerBlockType<BeforeAfterBlock>({
|
|
285
|
+
type: "beforeAfterBlock",
|
|
286
|
+
label: "Before / After",
|
|
287
|
+
description: "Drag-slider comparison between two images or videos",
|
|
288
|
+
category: "content",
|
|
289
|
+
iconGlyph: "◫",
|
|
290
|
+
schema: beforeAfterBlock,
|
|
291
|
+
defaultFactory: (key) => ({
|
|
292
|
+
_type: "beforeAfterBlock",
|
|
293
|
+
_key: key,
|
|
294
|
+
before_media_type: "image",
|
|
295
|
+
before_asset_path: "",
|
|
296
|
+
before_alt: "",
|
|
297
|
+
after_media_type: "image",
|
|
298
|
+
after_asset_path: "",
|
|
299
|
+
after_alt: "",
|
|
300
|
+
orientation: "horizontal",
|
|
301
|
+
initial_position: 50,
|
|
302
|
+
handle_color: "#FFFFFF",
|
|
303
|
+
width: "full",
|
|
304
|
+
aspect_ratio: "16:9",
|
|
305
|
+
video_autoplay: true,
|
|
306
|
+
video_loop: true,
|
|
307
|
+
video_muted: true,
|
|
308
|
+
border_radius: "",
|
|
309
|
+
shadow: false,
|
|
310
|
+
}),
|
|
311
|
+
renderer: BeforeAfterBlockRenderer as React.ComponentType<{ block: BeforeAfterBlock }>,
|
|
312
|
+
livePreview: LiveBeforeAfterPreview as unknown as React.ComponentType<{ block: BeforeAfterBlock; viewport?: import("./types").DeviceViewport; editable?: boolean }>,
|
|
313
|
+
editor: BeforeAfterBlockEditor as React.ComponentType<{ block: BeforeAfterBlock }>,
|
|
314
|
+
cardIcon: BeforeAfterBlockCardIcon,
|
|
315
|
+
compactIcon: BeforeAfterBlockIcon,
|
|
316
|
+
enterPresets: ["fade", "slide-up", "scale"],
|
|
317
|
+
hoverPresets: [],
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
registerBlockType<AudioBlock>({
|
|
321
|
+
type: "audioBlock",
|
|
322
|
+
label: "Audio",
|
|
323
|
+
description: "Minimal audio player with cover art and metadata",
|
|
324
|
+
category: "content",
|
|
325
|
+
iconGlyph: "♪",
|
|
326
|
+
schema: audioBlock,
|
|
327
|
+
defaultFactory: (key) => ({
|
|
328
|
+
_type: "audioBlock",
|
|
329
|
+
_key: key,
|
|
330
|
+
asset_path: "",
|
|
331
|
+
alt: "",
|
|
332
|
+
title: "",
|
|
333
|
+
artist: "",
|
|
334
|
+
cover_path: "",
|
|
335
|
+
accent_color: "#4794E2",
|
|
336
|
+
width: "contained",
|
|
337
|
+
border_radius: "",
|
|
338
|
+
shadow: false,
|
|
339
|
+
autoplay: false,
|
|
340
|
+
loop: false,
|
|
341
|
+
muted: false,
|
|
342
|
+
}),
|
|
343
|
+
renderer: AudioBlockRenderer as React.ComponentType<{ block: AudioBlock }>,
|
|
344
|
+
livePreview: LiveAudioPreview as unknown as React.ComponentType<{ block: AudioBlock; viewport?: import("./types").DeviceViewport; editable?: boolean }>,
|
|
345
|
+
editor: AudioBlockEditor as React.ComponentType<{ block: AudioBlock }>,
|
|
346
|
+
cardIcon: AudioBlockCardIcon,
|
|
347
|
+
compactIcon: AudioBlockIcon,
|
|
348
|
+
enterPresets: ["fade", "slide-up", "scale"],
|
|
349
|
+
hoverPresets: [],
|
|
350
|
+
});
|
|
351
|
+
|
|
270
352
|
// ── Section-level blocks ──
|
|
271
353
|
|
|
272
354
|
registerBlockType<ProjectGridBlock>({
|
package/lib/builder/types.ts
CHANGED
|
@@ -51,6 +51,8 @@ export const BLOCK_TYPE_REGISTRY: BlockTypeInfo[] = [
|
|
|
51
51
|
{ type: "videoBlock", label: "Video", description: "Vimeo, YouTube, or MP4", group: "generic", icon: "▶", category: "content" },
|
|
52
52
|
{ type: "spacerBlock", label: "Spacer", description: "Vertical spacing", group: "generic", icon: "↕", category: "content" },
|
|
53
53
|
{ type: "buttonBlock", label: "Button", description: "Call-to-action button", group: "generic", icon: "▣", category: "content" },
|
|
54
|
+
{ type: "beforeAfterBlock", label: "Before / After", description: "Drag-slider comparison between two images or videos", group: "generic", icon: "◫", category: "content" },
|
|
55
|
+
{ type: "audioBlock", label: "Audio", description: "Minimal audio player with cover art and metadata", group: "generic", icon: "♪", category: "content" },
|
|
54
56
|
];
|
|
55
57
|
|
|
56
58
|
/**
|
package/lib/sanity/types.ts
CHANGED
|
@@ -210,6 +210,62 @@ export interface ButtonBlock {
|
|
|
210
210
|
responsive?: ResponsiveOverrides<ButtonBlock>;
|
|
211
211
|
}
|
|
212
212
|
|
|
213
|
+
export interface BeforeAfterBlock {
|
|
214
|
+
_type: "beforeAfterBlock";
|
|
215
|
+
_key: string;
|
|
216
|
+
// Before side
|
|
217
|
+
before_media_type?: "image" | "video";
|
|
218
|
+
before_asset_path: string;
|
|
219
|
+
before_alt?: string;
|
|
220
|
+
// After side
|
|
221
|
+
after_media_type?: "image" | "video";
|
|
222
|
+
after_asset_path: string;
|
|
223
|
+
after_alt?: string;
|
|
224
|
+
// Slider
|
|
225
|
+
orientation?: "horizontal" | "vertical";
|
|
226
|
+
initial_position?: number; // 0–100
|
|
227
|
+
handle_color?: string; // hex
|
|
228
|
+
// Layout
|
|
229
|
+
width?: "full" | "contained" | "small" | "fill";
|
|
230
|
+
aspect_ratio?: "auto" | "16:9" | "4:3" | "1:1" | "21:9";
|
|
231
|
+
// Video playback (applies when either side is video)
|
|
232
|
+
video_autoplay?: boolean;
|
|
233
|
+
video_loop?: boolean;
|
|
234
|
+
video_muted?: boolean;
|
|
235
|
+
// Appearance
|
|
236
|
+
border_radius?: string;
|
|
237
|
+
shadow?: boolean;
|
|
238
|
+
enter_animation?: import("../../lib/animation/enter-types").EnterAnimationConfig;
|
|
239
|
+
hover_effect?: import("../../lib/animation/hover-effect-types").HoverEffectConfig;
|
|
240
|
+
layout?: BlockLayout;
|
|
241
|
+
responsive?: ResponsiveOverrides<BeforeAfterBlock>;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export interface AudioBlock {
|
|
245
|
+
_type: "audioBlock";
|
|
246
|
+
_key: string;
|
|
247
|
+
// Source
|
|
248
|
+
asset_path: string;
|
|
249
|
+
alt?: string;
|
|
250
|
+
// Metadata
|
|
251
|
+
title?: string;
|
|
252
|
+
artist?: string;
|
|
253
|
+
cover_path?: string;
|
|
254
|
+
// Appearance
|
|
255
|
+
accent_color?: string;
|
|
256
|
+
width?: "full" | "contained" | "small" | "fill";
|
|
257
|
+
border_radius?: string;
|
|
258
|
+
shadow?: boolean;
|
|
259
|
+
// Playback
|
|
260
|
+
autoplay?: boolean;
|
|
261
|
+
loop?: boolean;
|
|
262
|
+
muted?: boolean;
|
|
263
|
+
enter_animation?: import("../../lib/animation/enter-types").EnterAnimationConfig;
|
|
264
|
+
hover_effect?: import("../../lib/animation/hover-effect-types").HoverEffectConfig;
|
|
265
|
+
layout?: BlockLayout;
|
|
266
|
+
responsive?: ResponsiveOverrides<AudioBlock>;
|
|
267
|
+
}
|
|
268
|
+
|
|
213
269
|
// ============================================
|
|
214
270
|
// Project Grid Block v2 (template-only, Session 105)
|
|
215
271
|
// ============================================
|
|
@@ -635,6 +691,8 @@ export type ContentBlock =
|
|
|
635
691
|
| VideoBlock
|
|
636
692
|
| SpacerBlock
|
|
637
693
|
| ButtonBlock
|
|
694
|
+
| BeforeAfterBlock
|
|
695
|
+
| AudioBlock
|
|
638
696
|
| ProjectGridBlock
|
|
639
697
|
| ProjectCarouselBlock;
|
|
640
698
|
|
package/lib/version.ts
CHANGED
package/package.json
CHANGED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { defineField, defineType } from "sanity";
|
|
2
|
+
import { blockLayoutField, blockAnimationFields } from "./blockLayout";
|
|
3
|
+
|
|
4
|
+
export const audioBlock = defineType({
|
|
5
|
+
name: "audioBlock",
|
|
6
|
+
title: "Audio Block",
|
|
7
|
+
type: "object",
|
|
8
|
+
fields: [
|
|
9
|
+
// ── Source ──
|
|
10
|
+
defineField({
|
|
11
|
+
name: "asset_path",
|
|
12
|
+
title: "Audio File",
|
|
13
|
+
type: "string",
|
|
14
|
+
description: "Relative path to the audio file (mp3, wav, ogg, m4a, aac, flac)",
|
|
15
|
+
validation: (Rule) => Rule.required(),
|
|
16
|
+
}),
|
|
17
|
+
defineField({ name: "alt", title: "Alt Text", type: "string" }),
|
|
18
|
+
|
|
19
|
+
// ── Metadata ──
|
|
20
|
+
defineField({ name: "title", title: "Title", type: "string" }),
|
|
21
|
+
defineField({ name: "artist", title: "Artist", type: "string" }),
|
|
22
|
+
defineField({
|
|
23
|
+
name: "cover_path",
|
|
24
|
+
title: "Cover Art",
|
|
25
|
+
type: "string",
|
|
26
|
+
description: "Optional relative path to a cover image",
|
|
27
|
+
}),
|
|
28
|
+
|
|
29
|
+
// ── Appearance ──
|
|
30
|
+
defineField({
|
|
31
|
+
name: "accent_color",
|
|
32
|
+
title: "Accent Color",
|
|
33
|
+
type: "string",
|
|
34
|
+
description: "Hex color for the play button + progress fill",
|
|
35
|
+
initialValue: "#4794E2",
|
|
36
|
+
}),
|
|
37
|
+
defineField({
|
|
38
|
+
name: "width",
|
|
39
|
+
title: "Width",
|
|
40
|
+
type: "string",
|
|
41
|
+
options: {
|
|
42
|
+
list: [
|
|
43
|
+
{ title: "Full", value: "full" },
|
|
44
|
+
{ title: "Contained", value: "contained" },
|
|
45
|
+
{ title: "Small", value: "small" },
|
|
46
|
+
{ title: "Fill", value: "fill" },
|
|
47
|
+
],
|
|
48
|
+
},
|
|
49
|
+
initialValue: "contained",
|
|
50
|
+
}),
|
|
51
|
+
defineField({ name: "border_radius", title: "Border Radius", type: "string" }),
|
|
52
|
+
defineField({ name: "shadow", title: "Shadow", type: "boolean", initialValue: false }),
|
|
53
|
+
|
|
54
|
+
// ── Playback ──
|
|
55
|
+
defineField({ name: "autoplay", title: "Autoplay", type: "boolean", initialValue: false }),
|
|
56
|
+
defineField({ name: "loop", title: "Loop", type: "boolean", initialValue: false }),
|
|
57
|
+
defineField({ name: "muted", title: "Muted", type: "boolean", initialValue: false }),
|
|
58
|
+
|
|
59
|
+
...blockAnimationFields,
|
|
60
|
+
blockLayoutField,
|
|
61
|
+
],
|
|
62
|
+
preview: {
|
|
63
|
+
select: { path: "asset_path", title: "title", artist: "artist" },
|
|
64
|
+
prepare({ path, title, artist }) {
|
|
65
|
+
const label = title ? (artist ? `${title} — ${artist}` : title) : path || "Audio block";
|
|
66
|
+
return { title: label };
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
});
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { defineField, defineType } from "sanity";
|
|
2
|
+
import { blockLayoutField, blockAnimationFields } from "./blockLayout";
|
|
3
|
+
|
|
4
|
+
export const beforeAfterBlock = defineType({
|
|
5
|
+
name: "beforeAfterBlock",
|
|
6
|
+
title: "Before / After Block",
|
|
7
|
+
type: "object",
|
|
8
|
+
fields: [
|
|
9
|
+
// ── Before side ──
|
|
10
|
+
defineField({
|
|
11
|
+
name: "before_media_type",
|
|
12
|
+
title: "Before Media Type",
|
|
13
|
+
type: "string",
|
|
14
|
+
options: { list: [{ title: "Image", value: "image" }, { title: "Video", value: "video" }] },
|
|
15
|
+
initialValue: "image",
|
|
16
|
+
}),
|
|
17
|
+
defineField({
|
|
18
|
+
name: "before_asset_path",
|
|
19
|
+
title: "Before Asset Path",
|
|
20
|
+
type: "string",
|
|
21
|
+
description: "Relative path to the image or video file shown before the slider",
|
|
22
|
+
validation: (Rule) => Rule.required(),
|
|
23
|
+
}),
|
|
24
|
+
defineField({ name: "before_alt", title: "Before Alt Text", type: "string" }),
|
|
25
|
+
|
|
26
|
+
// ── After side ──
|
|
27
|
+
defineField({
|
|
28
|
+
name: "after_media_type",
|
|
29
|
+
title: "After Media Type",
|
|
30
|
+
type: "string",
|
|
31
|
+
options: { list: [{ title: "Image", value: "image" }, { title: "Video", value: "video" }] },
|
|
32
|
+
initialValue: "image",
|
|
33
|
+
}),
|
|
34
|
+
defineField({
|
|
35
|
+
name: "after_asset_path",
|
|
36
|
+
title: "After Asset Path",
|
|
37
|
+
type: "string",
|
|
38
|
+
description: "Relative path to the image or video file shown after the slider",
|
|
39
|
+
validation: (Rule) => Rule.required(),
|
|
40
|
+
}),
|
|
41
|
+
defineField({ name: "after_alt", title: "After Alt Text", type: "string" }),
|
|
42
|
+
|
|
43
|
+
// ── Slider behavior ──
|
|
44
|
+
defineField({
|
|
45
|
+
name: "orientation",
|
|
46
|
+
title: "Orientation",
|
|
47
|
+
type: "string",
|
|
48
|
+
options: {
|
|
49
|
+
list: [
|
|
50
|
+
{ title: "Horizontal (slider left-right)", value: "horizontal" },
|
|
51
|
+
{ title: "Vertical (slider top-bottom)", value: "vertical" },
|
|
52
|
+
],
|
|
53
|
+
},
|
|
54
|
+
initialValue: "horizontal",
|
|
55
|
+
}),
|
|
56
|
+
defineField({
|
|
57
|
+
name: "initial_position",
|
|
58
|
+
title: "Initial Position",
|
|
59
|
+
type: "number",
|
|
60
|
+
description: "Starting split position (0–100%)",
|
|
61
|
+
initialValue: 50,
|
|
62
|
+
validation: (Rule) => Rule.min(0).max(100),
|
|
63
|
+
}),
|
|
64
|
+
defineField({
|
|
65
|
+
name: "handle_color",
|
|
66
|
+
title: "Handle Color",
|
|
67
|
+
type: "string",
|
|
68
|
+
description: "Hex color for the slider line + handle",
|
|
69
|
+
initialValue: "#FFFFFF",
|
|
70
|
+
}),
|
|
71
|
+
|
|
72
|
+
// ── Layout ──
|
|
73
|
+
defineField({
|
|
74
|
+
name: "width",
|
|
75
|
+
title: "Width",
|
|
76
|
+
type: "string",
|
|
77
|
+
options: {
|
|
78
|
+
list: [
|
|
79
|
+
{ title: "Full", value: "full" },
|
|
80
|
+
{ title: "Contained", value: "contained" },
|
|
81
|
+
{ title: "Small", value: "small" },
|
|
82
|
+
{ title: "Fill", value: "fill" },
|
|
83
|
+
],
|
|
84
|
+
},
|
|
85
|
+
initialValue: "full",
|
|
86
|
+
}),
|
|
87
|
+
defineField({
|
|
88
|
+
name: "aspect_ratio",
|
|
89
|
+
title: "Aspect Ratio",
|
|
90
|
+
type: "string",
|
|
91
|
+
options: {
|
|
92
|
+
list: [
|
|
93
|
+
{ title: "Auto", value: "auto" },
|
|
94
|
+
{ title: "16:9", value: "16:9" },
|
|
95
|
+
{ title: "4:3", value: "4:3" },
|
|
96
|
+
{ title: "1:1", value: "1:1" },
|
|
97
|
+
{ title: "21:9", value: "21:9" },
|
|
98
|
+
],
|
|
99
|
+
},
|
|
100
|
+
initialValue: "16:9",
|
|
101
|
+
}),
|
|
102
|
+
|
|
103
|
+
// ── Video playback (applies when either side is video) ──
|
|
104
|
+
defineField({ name: "video_autoplay", title: "Video Autoplay", type: "boolean", initialValue: true }),
|
|
105
|
+
defineField({ name: "video_loop", title: "Video Loop", type: "boolean", initialValue: true }),
|
|
106
|
+
defineField({ name: "video_muted", title: "Video Muted", type: "boolean", initialValue: true }),
|
|
107
|
+
|
|
108
|
+
// ── Appearance ──
|
|
109
|
+
defineField({ name: "border_radius", title: "Border Radius", type: "string" }),
|
|
110
|
+
defineField({ name: "shadow", title: "Shadow", type: "boolean", initialValue: false }),
|
|
111
|
+
|
|
112
|
+
...blockAnimationFields,
|
|
113
|
+
blockLayoutField,
|
|
114
|
+
],
|
|
115
|
+
preview: {
|
|
116
|
+
select: { before: "before_asset_path", after: "after_asset_path" },
|
|
117
|
+
prepare({ before, after }) {
|
|
118
|
+
return { title: `Before/After: ${before || "—"} ↔ ${after || "—"}` };
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
});
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
// Block schemas (
|
|
1
|
+
// Block schemas (10)
|
|
2
2
|
export { textBlock } from "./textBlock";
|
|
3
3
|
export { imageBlock } from "./imageBlock";
|
|
4
4
|
export { imageGridBlock } from "./imageGridBlock";
|
|
5
5
|
export { videoBlock } from "./videoBlock";
|
|
6
6
|
export { spacerBlock } from "./spacerBlock";
|
|
7
7
|
export { buttonBlock } from "./buttonBlock";
|
|
8
|
+
export { beforeAfterBlock } from "./beforeAfterBlock";
|
|
9
|
+
export { audioBlock } from "./audioBlock";
|
|
8
10
|
export { projectGridBlock } from "./projectGridBlock";
|
|
9
11
|
export { projectCarouselBlock } from "./projectCarouselBlock";
|
package/sanity/schemas/index.ts
CHANGED
|
@@ -18,6 +18,8 @@ import {
|
|
|
18
18
|
videoBlock,
|
|
19
19
|
spacerBlock,
|
|
20
20
|
buttonBlock,
|
|
21
|
+
beforeAfterBlock,
|
|
22
|
+
audioBlock,
|
|
21
23
|
projectGridBlock,
|
|
22
24
|
projectCarouselBlock,
|
|
23
25
|
} from "./blocks";
|
|
@@ -69,6 +71,8 @@ export {
|
|
|
69
71
|
videoBlock,
|
|
70
72
|
spacerBlock,
|
|
71
73
|
buttonBlock,
|
|
74
|
+
beforeAfterBlock,
|
|
75
|
+
audioBlock,
|
|
72
76
|
projectGridBlock,
|
|
73
77
|
projectCarouselBlock,
|
|
74
78
|
} from "./blocks";
|
|
@@ -93,13 +97,15 @@ export const schemaTypes = [
|
|
|
93
97
|
parallaxGroup, // Parallax V2 group (Session 123)
|
|
94
98
|
coverSection, // Cover Section — proportional rows (Session 176)
|
|
95
99
|
|
|
96
|
-
// Blocks (
|
|
100
|
+
// Blocks (10)
|
|
97
101
|
textBlock,
|
|
98
102
|
imageBlock,
|
|
99
103
|
imageGridBlock,
|
|
100
104
|
videoBlock,
|
|
101
105
|
spacerBlock,
|
|
102
106
|
buttonBlock,
|
|
107
|
+
beforeAfterBlock,
|
|
108
|
+
audioBlock,
|
|
103
109
|
projectGridBlock,
|
|
104
110
|
projectCarouselBlock,
|
|
105
111
|
];
|