@morphika/andami 0.2.13 → 0.2.14
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/components/blocks/ImageBlockRenderer.tsx +12 -10
- package/components/blocks/VideoBlockRenderer.tsx +11 -6
- package/components/builder/editors/ImageBlockEditor.tsx +1 -0
- package/components/builder/editors/VideoBlockEditor.tsx +1 -0
- package/components/builder/live-preview/LiveImagePreview.tsx +21 -2
- package/components/builder/live-preview/LiveVideoPreview.tsx +8 -3
- package/lib/sanity/queries.ts +18 -4
- package/lib/sanity/types.ts +2 -2
- package/lib/version.ts +1 -1
- package/package.json +1 -1
- package/sanity/schemas/blocks/imageBlock.ts +1 -0
- package/sanity/schemas/blocks/videoBlock.ts +1 -0
|
@@ -25,24 +25,26 @@ const aspectMap: Record<string, string | undefined> = {
|
|
|
25
25
|
export default function ImageBlockRenderer({ block }: { block: ImageBlock }) {
|
|
26
26
|
const resolveAsset = useAssetUrl();
|
|
27
27
|
const src = resolveAsset(block.asset_path);
|
|
28
|
-
const
|
|
29
|
-
const
|
|
28
|
+
const isFill = block.width === "fill";
|
|
29
|
+
const widthStyle = isFill ? {} : (widthStyleMap[block.width ?? "full"] || widthStyleMap.full);
|
|
30
|
+
const aspect = isFill ? undefined : aspectMap[block.aspect_ratio ?? "auto"];
|
|
30
31
|
|
|
31
32
|
// BLK-014: Strip any existing unit suffix, then validate as a number before appending px
|
|
32
33
|
const rawRadius = block.border_radius ? String(block.border_radius).replace(/[a-z%]+$/i, "") : "";
|
|
33
34
|
const borderRadius = rawRadius && !isNaN(Number(rawRadius)) ? `${rawRadius}px` : undefined;
|
|
34
35
|
|
|
35
|
-
const imgStyle: React.CSSProperties =
|
|
36
|
-
width: "100%",
|
|
37
|
-
display: "block",
|
|
38
|
-
objectFit: aspect ? "cover" : undefined,
|
|
39
|
-
aspectRatio: aspect,
|
|
40
|
-
};
|
|
36
|
+
const imgStyle: React.CSSProperties = isFill
|
|
37
|
+
? { position: "absolute", inset: 0, width: "100%", height: "100%", objectFit: "cover", display: "block" }
|
|
38
|
+
: { width: "100%", display: "block", objectFit: aspect ? "cover" : undefined, aspectRatio: aspect };
|
|
41
39
|
|
|
42
40
|
const imgClassName = block.shadow ? "shadow-lg" : "";
|
|
43
41
|
|
|
42
|
+
const figureStyle: React.CSSProperties = isFill
|
|
43
|
+
? { position: "absolute", inset: 0, borderRadius, overflow: "hidden" }
|
|
44
|
+
: { ...widthStyle, borderRadius, overflow: "hidden" };
|
|
45
|
+
|
|
44
46
|
return (
|
|
45
|
-
<figure style={
|
|
47
|
+
<figure style={figureStyle}>
|
|
46
48
|
{/* eslint-disable-next-line @next/next/no-img-element */}
|
|
47
49
|
<img
|
|
48
50
|
src={src}
|
|
@@ -53,7 +55,7 @@ export default function ImageBlockRenderer({ block }: { block: ImageBlock }) {
|
|
|
53
55
|
style={imgStyle}
|
|
54
56
|
className={imgClassName}
|
|
55
57
|
/>
|
|
56
|
-
{block.caption && (
|
|
58
|
+
{!isFill && block.caption && (
|
|
57
59
|
<figcaption className="mt-2 font-sans text-xs uppercase tracking-wider text-brand-muted">
|
|
58
60
|
{block.caption}
|
|
59
61
|
</figcaption>
|
|
@@ -286,18 +286,23 @@ function NativeVideo({ block, paddingBottom, resolveAsset }: {
|
|
|
286
286
|
|
|
287
287
|
export default function VideoBlockRenderer({ block }: { block: VideoBlock }) {
|
|
288
288
|
const resolveAsset = useAssetUrl();
|
|
289
|
-
const
|
|
290
|
-
const
|
|
289
|
+
const isFill = block.width === "fill";
|
|
290
|
+
const widthStyle = isFill ? {} : (widthStyleMap[block.width ?? "full"] || widthStyleMap.full);
|
|
291
|
+
const paddingBottom = isFill ? "100%" : (aspectMap[block.aspect_ratio ?? "16:9"] || "56.25%");
|
|
291
292
|
const borderRadius = block.border_radius ? `${String(block.border_radius).replace(/px$/i, "")}px` : undefined;
|
|
292
293
|
|
|
294
|
+
const containerStyle: React.CSSProperties = isFill
|
|
295
|
+
? { position: "absolute", inset: 0, borderRadius, overflow: "hidden" }
|
|
296
|
+
: { ...widthStyle, borderRadius, overflow: borderRadius ? "hidden" : undefined };
|
|
297
|
+
|
|
293
298
|
return (
|
|
294
|
-
<div style={
|
|
299
|
+
<div style={containerStyle}>
|
|
295
300
|
{block.video_type === "vimeo" ? (
|
|
296
|
-
<VimeoEmbed block={block} paddingBottom={paddingBottom} />
|
|
301
|
+
<VimeoEmbed block={block} paddingBottom={isFill ? "100%" : paddingBottom} />
|
|
297
302
|
) : block.video_type === "youtube" ? (
|
|
298
|
-
<YouTubeEmbed block={block} paddingBottom={paddingBottom} />
|
|
303
|
+
<YouTubeEmbed block={block} paddingBottom={isFill ? "100%" : paddingBottom} />
|
|
299
304
|
) : (
|
|
300
|
-
<NativeVideo block={block} paddingBottom={paddingBottom} resolveAsset={resolveAsset} />
|
|
305
|
+
<NativeVideo block={block} paddingBottom={isFill ? "100%" : paddingBottom} resolveAsset={resolveAsset} />
|
|
301
306
|
)}
|
|
302
307
|
</div>
|
|
303
308
|
);
|
|
@@ -113,6 +113,7 @@ export default function ImageBlockEditor({ block }: Props) {
|
|
|
113
113
|
{ value: "full", label: "100%" },
|
|
114
114
|
{ value: "contained", label: "75%" },
|
|
115
115
|
{ value: "small", label: "50%" },
|
|
116
|
+
{ value: "fill", label: "Fill" },
|
|
116
117
|
] as const
|
|
117
118
|
).map((opt) => (
|
|
118
119
|
<button
|
|
@@ -21,8 +21,10 @@ export default function LiveImagePreview({ block }: { block: ImageBlock }) {
|
|
|
21
21
|
const thumbSrc = adminThumbUrl(block.asset_path);
|
|
22
22
|
const fullSrc = adminAssetUrl(block.asset_path);
|
|
23
23
|
const src = useFallback ? fullSrc : thumbSrc;
|
|
24
|
-
const
|
|
25
|
-
|
|
24
|
+
const isFill = block.width === "fill";
|
|
25
|
+
const widthStyle = isFill
|
|
26
|
+
? "100%"
|
|
27
|
+
: block.width === "contained"
|
|
26
28
|
? "75%"
|
|
27
29
|
: block.width === "small"
|
|
28
30
|
? "50%"
|
|
@@ -35,6 +37,23 @@ export default function LiveImagePreview({ block }: { block: ImageBlock }) {
|
|
|
35
37
|
"21:9": "21/9",
|
|
36
38
|
};
|
|
37
39
|
|
|
40
|
+
if (isFill) {
|
|
41
|
+
return (
|
|
42
|
+
<div style={{ position: "absolute", inset: 0, overflow: "hidden", borderRadius: block.border_radius ? `${String(block.border_radius).replace(/px$/i, "")}px` : undefined }}>
|
|
43
|
+
{/* eslint-disable-next-line @next/next/no-img-element */}
|
|
44
|
+
<img
|
|
45
|
+
src={src}
|
|
46
|
+
alt={block.alt || ""}
|
|
47
|
+
onLoad={() => setImgLoaded(true)}
|
|
48
|
+
onError={() => {
|
|
49
|
+
if (!useFallback) { setUseFallback(true); } else { setImgError(true); }
|
|
50
|
+
}}
|
|
51
|
+
style={{ width: "100%", height: "100%", objectFit: "cover" }}
|
|
52
|
+
/>
|
|
53
|
+
</div>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
38
57
|
return (
|
|
39
58
|
<div style={{ width: widthStyle, margin: block.width !== "full" ? "0 auto" : undefined }}>
|
|
40
59
|
{imgError ? (
|
|
@@ -30,8 +30,9 @@ export default function LiveVideoPreview({ block }: { block: VideoBlock }) {
|
|
|
30
30
|
"4:3": "75%",
|
|
31
31
|
auto: "56.25%",
|
|
32
32
|
};
|
|
33
|
+
const isFill = block.width === "fill";
|
|
33
34
|
const paddingBottom = aspectMap[block.aspect_ratio || "16:9"] || "56.25%";
|
|
34
|
-
const widthStyle = block.width === "contained" ? "75%" : "100%";
|
|
35
|
+
const widthStyle = isFill ? "100%" : (block.width === "contained" ? "75%" : "100%");
|
|
35
36
|
|
|
36
37
|
// Resolve thumbnail URL based on video type (no iframes, no streaming)
|
|
37
38
|
let thumbnailUrl: string | null = null;
|
|
@@ -59,9 +60,13 @@ export default function LiveVideoPreview({ block }: { block: VideoBlock }) {
|
|
|
59
60
|
|
|
60
61
|
const borderRadius = block.border_radius ? `${String(block.border_radius).replace(/px$/i, "")}px` : undefined;
|
|
61
62
|
|
|
63
|
+
const outerStyle: React.CSSProperties = isFill
|
|
64
|
+
? { position: "absolute", inset: 0, minWidth: 0, borderRadius, overflow: "hidden" }
|
|
65
|
+
: { width: widthStyle, margin: block.width === "contained" ? "0 auto" : undefined, minWidth: 0, borderRadius, overflow: borderRadius ? "hidden" : undefined };
|
|
66
|
+
|
|
62
67
|
return (
|
|
63
|
-
<div style={
|
|
64
|
-
<div style={{ position: "relative", paddingBottom, overflow: "hidden", background: "#000", lineHeight: 0, fontSize: 0, borderRadius: "inherit" }}>
|
|
68
|
+
<div style={outerStyle}>
|
|
69
|
+
<div style={{ position: "relative", paddingBottom: isFill ? undefined : paddingBottom, height: isFill ? "100%" : undefined, overflow: "hidden", background: "#000", lineHeight: 0, fontSize: 0, borderRadius: "inherit" }}>
|
|
65
70
|
{thumbnailUrl ? (
|
|
66
71
|
<>
|
|
67
72
|
{/* eslint-disable-next-line @next/next/no-img-element */}
|
package/lib/sanity/queries.ts
CHANGED
|
@@ -5,14 +5,14 @@ import { groq } from "next-sanity";
|
|
|
5
5
|
// ============================================
|
|
6
6
|
|
|
7
7
|
// Deep expansion of content_rows.
|
|
8
|
-
// Handles PageSectionV2, CustomSectionInstance, and
|
|
8
|
+
// Handles PageSectionV2, CustomSectionInstance, ParallaxGroup, and CoverSection.
|
|
9
9
|
// GROQ projections are additive — fields that don't exist on an object are omitted.
|
|
10
10
|
const blockExpansion = `
|
|
11
11
|
content_rows[] {
|
|
12
12
|
_key,
|
|
13
13
|
_type,
|
|
14
14
|
section_type,
|
|
15
|
-
// ── V2 section columns ──
|
|
15
|
+
// ── V2 section columns (shared by PageSectionV2 and CoverSection) ──
|
|
16
16
|
columns[] {
|
|
17
17
|
_key,
|
|
18
18
|
_type,
|
|
@@ -73,9 +73,23 @@ const blockExpansion = `
|
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
75
|
},
|
|
76
|
-
// ──
|
|
76
|
+
// ── CoverSection fields (only present when _type == "coverSection") ──
|
|
77
|
+
background_type,
|
|
78
|
+
background_image,
|
|
79
|
+
background_video,
|
|
80
|
+
background_position,
|
|
81
|
+
background_size,
|
|
82
|
+
background_overlay_color,
|
|
83
|
+
background_overlay_opacity,
|
|
84
|
+
height,
|
|
85
|
+
cover_rows[] {
|
|
86
|
+
_key,
|
|
87
|
+
height_percent,
|
|
88
|
+
vertical_align
|
|
89
|
+
},
|
|
90
|
+
// ── Settings (V2 section settings + CoverSection settings) ──
|
|
77
91
|
settings,
|
|
78
|
-
// ── Responsive overrides (V2
|
|
92
|
+
// ── Responsive overrides (V2 + CoverSection store responsive at section level) ──
|
|
79
93
|
responsive
|
|
80
94
|
}
|
|
81
95
|
`;
|
package/lib/sanity/types.ts
CHANGED
|
@@ -135,7 +135,7 @@ export interface ImageBlock {
|
|
|
135
135
|
asset_path: string;
|
|
136
136
|
alt?: string;
|
|
137
137
|
caption?: string;
|
|
138
|
-
width?: "full" | "contained" | "small";
|
|
138
|
+
width?: "full" | "contained" | "small" | "fill";
|
|
139
139
|
aspect_ratio?: "auto" | "16:9" | "4:3" | "1:1" | "21:9";
|
|
140
140
|
lazy?: boolean;
|
|
141
141
|
border_radius?: string;
|
|
@@ -174,7 +174,7 @@ export interface VideoBlock {
|
|
|
174
174
|
loop?: boolean;
|
|
175
175
|
muted?: boolean;
|
|
176
176
|
controls?: boolean;
|
|
177
|
-
width?: "full" | "contained";
|
|
177
|
+
width?: "full" | "contained" | "fill";
|
|
178
178
|
aspect_ratio?: "16:9" | "21:9" | "4:3" | "auto";
|
|
179
179
|
border_radius?: string;
|
|
180
180
|
enter_animation?: import("../../lib/animation/enter-types").EnterAnimationConfig;
|
package/lib/version.ts
CHANGED
package/package.json
CHANGED