@morphika/andami 0.2.2 → 0.2.4
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.
|
@@ -226,8 +226,14 @@ export default function ProjectGridBlockRenderer({
|
|
|
226
226
|
// animations at once (with stagger). No per-card observer needed.
|
|
227
227
|
const [gridVisible, setGridVisible] = useState(false);
|
|
228
228
|
|
|
229
|
+
// Track whether we have projects loaded — needed as a dep so the
|
|
230
|
+
// IntersectionObserver effect re-runs after the async fetch populates
|
|
231
|
+
// resolvedProjects (on the first render the grid div isn't mounted yet
|
|
232
|
+
// because the component returns null when resolvedProjects is empty).
|
|
233
|
+
const hasProjects = resolvedProjects.length > 0;
|
|
234
|
+
|
|
229
235
|
useEffect(() => {
|
|
230
|
-
if (!entranceEnabled || gridVisible) return;
|
|
236
|
+
if (!entranceEnabled || gridVisible || !hasProjects) return;
|
|
231
237
|
const el = containerRef.current;
|
|
232
238
|
if (!el) return;
|
|
233
239
|
|
|
@@ -244,12 +250,18 @@ export default function ProjectGridBlockRenderer({
|
|
|
244
250
|
observer.disconnect();
|
|
245
251
|
}
|
|
246
252
|
},
|
|
247
|
-
{
|
|
253
|
+
{
|
|
254
|
+
threshold: 0.01,
|
|
255
|
+
// Trigger 200px before the grid scrolls into view so cards
|
|
256
|
+
// start animating just before the user sees them (especially
|
|
257
|
+
// on mobile where the grid may sit just below the fold).
|
|
258
|
+
rootMargin: "200px 0px",
|
|
259
|
+
},
|
|
248
260
|
);
|
|
249
261
|
|
|
250
262
|
observer.observe(el);
|
|
251
263
|
return () => observer.disconnect();
|
|
252
|
-
}, [entranceEnabled, gridVisible]);
|
|
264
|
+
}, [entranceEnabled, gridVisible, hasProjects]);
|
|
253
265
|
|
|
254
266
|
// ─── Compute stagger indices sorted by vertical position ───
|
|
255
267
|
// Cards at similar y positions (within half of gapV) share the same rank.
|
|
@@ -330,6 +342,10 @@ export default function ProjectGridBlockRenderer({
|
|
|
330
342
|
top: item.y,
|
|
331
343
|
width: item.width,
|
|
332
344
|
height: item.height,
|
|
345
|
+
// Clip the card when hover scale-up overflows the cell bounds
|
|
346
|
+
// so border-radius is preserved visually.
|
|
347
|
+
overflow: "hidden",
|
|
348
|
+
borderRadius: borderRadius > 0 ? borderRadius : undefined,
|
|
333
349
|
}}
|
|
334
350
|
>
|
|
335
351
|
{entranceEnabled && entranceAnimConfig ? (
|
|
@@ -428,9 +444,12 @@ const ProjectCard = memo(function ProjectCard({
|
|
|
428
444
|
const handlePlay = useCallback(() => setIsPlaying(true), []);
|
|
429
445
|
const handlePause = useCallback(() => setIsPlaying(false), []);
|
|
430
446
|
|
|
431
|
-
// Scale
|
|
432
|
-
|
|
433
|
-
|
|
447
|
+
// Scale hover: applied to an inner content wrapper so the zoom is
|
|
448
|
+
// visible inside the card's borderRadius + overflow:hidden boundary
|
|
449
|
+
// (the parent masonry cell also clips, so scaling the card wrapper
|
|
450
|
+
// itself would be invisible).
|
|
451
|
+
const contentScale =
|
|
452
|
+
hoverEffect === "scale" && hovered ? "scale(1.05)" : "scale(1)";
|
|
434
453
|
|
|
435
454
|
return (
|
|
436
455
|
<Link
|
|
@@ -449,15 +468,22 @@ const ProjectCard = memo(function ProjectCard({
|
|
|
449
468
|
height: "100%",
|
|
450
469
|
overflow: "hidden",
|
|
451
470
|
borderRadius: radius,
|
|
471
|
+
// 3D tilt: applied on the card wrapper (set via handleMouseMove)
|
|
452
472
|
transition:
|
|
453
473
|
hoverEffect === "3d"
|
|
454
474
|
? "transform 100ms ease-out"
|
|
455
|
-
:
|
|
456
|
-
? "transform 300ms ease"
|
|
457
|
-
: undefined,
|
|
458
|
-
transform: hoverEffect === "scale" ? scaleTransform : undefined,
|
|
475
|
+
: undefined,
|
|
459
476
|
}}
|
|
460
477
|
>
|
|
478
|
+
{/* Inner content wrapper — receives scale hover transform */}
|
|
479
|
+
<div
|
|
480
|
+
style={{
|
|
481
|
+
position: "absolute",
|
|
482
|
+
inset: 0,
|
|
483
|
+
transition: hoverEffect === "scale" ? "transform 300ms ease" : undefined,
|
|
484
|
+
transform: hoverEffect === "scale" ? contentScale : undefined,
|
|
485
|
+
}}
|
|
486
|
+
>
|
|
461
487
|
{/* Thumbnail image */}
|
|
462
488
|
{imgSrc ? (
|
|
463
489
|
<img
|
|
@@ -522,7 +548,9 @@ const ProjectCard = memo(function ProjectCard({
|
|
|
522
548
|
/>
|
|
523
549
|
)}
|
|
524
550
|
|
|
525
|
-
{/*
|
|
551
|
+
</div>{/* end inner content wrapper */}
|
|
552
|
+
|
|
553
|
+
{/* Subtitle overlay — outside the scale wrapper so text stays crisp */}
|
|
526
554
|
{showSubtitle && (
|
|
527
555
|
<div
|
|
528
556
|
style={{
|
|
@@ -10,6 +10,7 @@ import { useBuilderStore } from "../../lib/builder/store";
|
|
|
10
10
|
import { makeBlockId, makeColumnDroppableId } from "./DndWrapper";
|
|
11
11
|
import type { SectionColumn, ContentBlock, PageSectionV2 } from "../../lib/sanity/types";
|
|
12
12
|
import { getColumnVerticalAlign } from "../../lib/builder/layout-styles";
|
|
13
|
+
import { isSectionBlockType } from "../../lib/builder/types";
|
|
13
14
|
import { BUILDER_BLUE } from "../../lib/builder/constants";
|
|
14
15
|
|
|
15
16
|
// ============================================
|
|
@@ -174,10 +175,26 @@ export default function SectionV2Column({
|
|
|
174
175
|
);
|
|
175
176
|
|
|
176
177
|
// ---- Stable callbacks ----
|
|
178
|
+
|
|
179
|
+
// When the column contains a single section-level block (e.g. projectGridBlock),
|
|
180
|
+
// that block fills the entire column visually. Clicking anywhere in the column
|
|
181
|
+
// should select the block — not the column — because users expect to reach the
|
|
182
|
+
// block's settings panel (Settings / Layout / Animation tabs).
|
|
183
|
+
const selectBlock = useBuilderStore((s) => s.selectBlock);
|
|
184
|
+
const singleSectionBlock = (() => {
|
|
185
|
+
const blocks = column.blocks || [];
|
|
186
|
+
if (blocks.length === 1 && isSectionBlockType(blocks[0]._type)) return blocks[0];
|
|
187
|
+
return null;
|
|
188
|
+
})();
|
|
189
|
+
|
|
177
190
|
const handleClick = useCallback((e: React.MouseEvent) => {
|
|
178
191
|
e.stopPropagation();
|
|
179
|
-
|
|
180
|
-
|
|
192
|
+
if (singleSectionBlock) {
|
|
193
|
+
selectBlock(singleSectionBlock._key);
|
|
194
|
+
} else {
|
|
195
|
+
onSelect();
|
|
196
|
+
}
|
|
197
|
+
}, [onSelect, singleSectionBlock, selectBlock]);
|
|
181
198
|
|
|
182
199
|
const handleMouseEnter = useCallback(() => setIsHovered(true), []);
|
|
183
200
|
const handleMouseLeave = useCallback(() => setIsHovered(false), []);
|
package/package.json
CHANGED