@opensite/ui 3.10.0 → 3.11.2
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/instagram-post-grid.cjs +344 -114
- package/dist/instagram-post-grid.d.cts +34 -11
- package/dist/instagram-post-grid.d.ts +34 -11
- package/dist/instagram-post-grid.js +344 -114
- package/dist/registry.cjs +424 -131
- package/dist/registry.js +424 -131
- package/dist/social-link-icon.d.cts +1 -1
- package/dist/social-link-icon.d.ts +1 -1
- package/package.json +2 -1
|
@@ -2,11 +2,9 @@
|
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
var React = require('react');
|
|
5
|
+
var mediaImmersive = require('@page-speed/media-immersive');
|
|
5
6
|
var clsx = require('clsx');
|
|
6
7
|
var tailwindMerge = require('tailwind-merge');
|
|
7
|
-
var img = require('@page-speed/img');
|
|
8
|
-
var video = require('@page-speed/video');
|
|
9
|
-
var pressable = require('@page-speed/pressable');
|
|
10
8
|
var icon = require('@page-speed/icon');
|
|
11
9
|
var jsxRuntime = require('react/jsx-runtime');
|
|
12
10
|
|
|
@@ -423,23 +421,323 @@ var Section = React__namespace.default.forwardRef(
|
|
|
423
421
|
}
|
|
424
422
|
);
|
|
425
423
|
Section.displayName = "Section";
|
|
426
|
-
|
|
424
|
+
var IG_TILE_WIDTH = 320;
|
|
425
|
+
var IG_TILE_STYLE = { width: "100%" };
|
|
426
|
+
var IG_TILE_RADIUS = 18;
|
|
427
|
+
var TILE_WRAPPER_STYLE = {
|
|
428
|
+
position: "relative",
|
|
429
|
+
borderRadius: IG_TILE_RADIUS,
|
|
430
|
+
boxShadow: "0 1px 2px rgba(15, 23, 42, 0.08), 0 6px 16px rgba(15, 23, 42, 0.10)"
|
|
431
|
+
};
|
|
432
|
+
var CAPTION_SCRIM_STYLE = {
|
|
433
|
+
position: "absolute",
|
|
434
|
+
left: 0,
|
|
435
|
+
right: 0,
|
|
436
|
+
bottom: 0,
|
|
437
|
+
padding: "28px 14px 13px",
|
|
438
|
+
background: "linear-gradient(180deg, rgba(8,12,24,0) 0%, rgba(8,12,24,0.55) 45%, rgba(8,12,24,0.78) 100%)",
|
|
439
|
+
borderBottomLeftRadius: IG_TILE_RADIUS,
|
|
440
|
+
borderBottomRightRadius: IG_TILE_RADIUS,
|
|
441
|
+
pointerEvents: "none"
|
|
442
|
+
};
|
|
443
|
+
var CAPTION_TEXT_STYLE = {
|
|
444
|
+
color: "#fff",
|
|
445
|
+
fontSize: 13,
|
|
446
|
+
lineHeight: 1.4,
|
|
447
|
+
fontWeight: 600,
|
|
448
|
+
letterSpacing: "0.005em",
|
|
449
|
+
textShadow: "0 1px 2px rgba(0, 0, 0, 0.35)",
|
|
450
|
+
display: "-webkit-box",
|
|
451
|
+
WebkitLineClamp: 2,
|
|
452
|
+
WebkitBoxOrient: "vertical",
|
|
453
|
+
overflow: "hidden",
|
|
454
|
+
height: "2.8em"
|
|
455
|
+
};
|
|
456
|
+
var TITLE_MAX = 90;
|
|
457
|
+
function truncate(text, max = TITLE_MAX) {
|
|
458
|
+
const trimmed = text.trim();
|
|
459
|
+
if (trimmed.length <= max) return trimmed;
|
|
460
|
+
const slice = trimmed.slice(0, max);
|
|
461
|
+
const lastSpace = slice.lastIndexOf(" ");
|
|
462
|
+
const head = lastSpace > max * 0.6 ? slice.slice(0, lastSpace) : slice;
|
|
463
|
+
return `${head.trimEnd()}\u2026`;
|
|
464
|
+
}
|
|
465
|
+
function captionToString(caption) {
|
|
466
|
+
return typeof caption === "string" ? caption : "";
|
|
467
|
+
}
|
|
468
|
+
function toMediaItem(item) {
|
|
469
|
+
const isVideo = Boolean(item.isVideo && item.videoUrl);
|
|
470
|
+
const fullCaption = captionToString(item.caption);
|
|
471
|
+
const title = truncate(fullCaption) || item.imageAlt || "Instagram post";
|
|
472
|
+
const meta = {
|
|
473
|
+
href: item.href,
|
|
474
|
+
likeCount: item.likeCount,
|
|
475
|
+
commentCount: item.commentCount,
|
|
476
|
+
viewCount: item.viewCount,
|
|
477
|
+
date: item.date
|
|
478
|
+
};
|
|
479
|
+
return {
|
|
480
|
+
id: item.id,
|
|
481
|
+
type: isVideo ? "video" : "image",
|
|
482
|
+
poster: item.image,
|
|
483
|
+
// Video source only when the post is a real video (image posts ignore it).
|
|
484
|
+
src: isVideo ? item.videoUrl : void 0,
|
|
485
|
+
title,
|
|
486
|
+
caption: fullCaption || void 0,
|
|
487
|
+
kind: "Instagram",
|
|
488
|
+
meta
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
function openPermalink(item) {
|
|
492
|
+
if (typeof window === "undefined") return;
|
|
493
|
+
const href = item.meta?.href;
|
|
494
|
+
if (!href) return;
|
|
495
|
+
window.open(href, "_blank", "noopener,noreferrer");
|
|
496
|
+
}
|
|
497
|
+
function likeBadge(likeCount) {
|
|
498
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
499
|
+
"span",
|
|
500
|
+
{
|
|
501
|
+
"aria-label": `${likeCount.toLocaleString()} likes`,
|
|
502
|
+
style: {
|
|
503
|
+
display: "inline-flex",
|
|
504
|
+
alignItems: "center",
|
|
505
|
+
gap: 6,
|
|
506
|
+
// Breathing room from the tile's top-left corner (refinement #1).
|
|
507
|
+
margin: 6,
|
|
508
|
+
padding: "6px 11px",
|
|
509
|
+
borderRadius: 999,
|
|
510
|
+
background: "rgba(8, 12, 24, 0.55)",
|
|
511
|
+
backdropFilter: "blur(8px)",
|
|
512
|
+
WebkitBackdropFilter: "blur(8px)",
|
|
513
|
+
border: "1px solid rgba(255, 255, 255, 0.14)",
|
|
514
|
+
color: "#fff",
|
|
515
|
+
fontSize: 13,
|
|
516
|
+
fontWeight: 600,
|
|
517
|
+
lineHeight: 1,
|
|
518
|
+
letterSpacing: "0.01em"
|
|
519
|
+
},
|
|
520
|
+
children: [
|
|
521
|
+
/* @__PURE__ */ jsxRuntime.jsx(DynamicIcon, { name: "lucide/heart", size: 15, "aria-hidden": "true" }),
|
|
522
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", children: likeCount.toLocaleString() })
|
|
523
|
+
]
|
|
524
|
+
}
|
|
525
|
+
);
|
|
526
|
+
}
|
|
527
|
+
var VIEWER_ACTIONS = [
|
|
528
|
+
{
|
|
529
|
+
id: "open-in-instagram",
|
|
530
|
+
icon: /* @__PURE__ */ jsxRuntime.jsx(DynamicIcon, { name: "lucide/external-link", size: 22, "aria-hidden": "true" }),
|
|
531
|
+
label: "Instagram",
|
|
532
|
+
ariaLabel: "Open in Instagram",
|
|
533
|
+
onPress: (item) => openPermalink(item)
|
|
534
|
+
}
|
|
535
|
+
];
|
|
536
|
+
function RailStat({
|
|
427
537
|
iconName,
|
|
428
538
|
count,
|
|
429
|
-
label
|
|
539
|
+
label,
|
|
540
|
+
onPress
|
|
430
541
|
}) {
|
|
431
542
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
432
|
-
"
|
|
543
|
+
"button",
|
|
433
544
|
{
|
|
434
|
-
|
|
545
|
+
type: "button",
|
|
435
546
|
"aria-label": `${count.toLocaleString()} ${label}`,
|
|
547
|
+
onClick: (e) => {
|
|
548
|
+
e.stopPropagation();
|
|
549
|
+
onPress();
|
|
550
|
+
},
|
|
551
|
+
style: {
|
|
552
|
+
display: "flex",
|
|
553
|
+
flexDirection: "column",
|
|
554
|
+
alignItems: "center",
|
|
555
|
+
gap: 5,
|
|
556
|
+
padding: 0,
|
|
557
|
+
border: "none",
|
|
558
|
+
background: "transparent",
|
|
559
|
+
color: "inherit",
|
|
560
|
+
cursor: "pointer",
|
|
561
|
+
font: "inherit"
|
|
562
|
+
},
|
|
563
|
+
children: [
|
|
564
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
565
|
+
"span",
|
|
566
|
+
{
|
|
567
|
+
style: {
|
|
568
|
+
width: 46,
|
|
569
|
+
height: 46,
|
|
570
|
+
borderRadius: "50%",
|
|
571
|
+
background: "var(--psmi-chrome-bg, rgba(255,255,255,0.14))",
|
|
572
|
+
backdropFilter: "blur(6px)",
|
|
573
|
+
WebkitBackdropFilter: "blur(6px)",
|
|
574
|
+
display: "flex",
|
|
575
|
+
alignItems: "center",
|
|
576
|
+
justifyContent: "center"
|
|
577
|
+
},
|
|
578
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(DynamicIcon, { name: iconName, size: 22, "aria-hidden": "true" })
|
|
579
|
+
}
|
|
580
|
+
),
|
|
581
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", style: { fontSize: 11, fontWeight: 600 }, children: count.toLocaleString() })
|
|
582
|
+
]
|
|
583
|
+
}
|
|
584
|
+
);
|
|
585
|
+
}
|
|
586
|
+
function InstagramViewerRail({ item }) {
|
|
587
|
+
const meta = item.meta ?? {};
|
|
588
|
+
const open = () => openPermalink(item);
|
|
589
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
590
|
+
"div",
|
|
591
|
+
{
|
|
592
|
+
style: {
|
|
593
|
+
position: "absolute",
|
|
594
|
+
right: 11,
|
|
595
|
+
bottom: 135,
|
|
596
|
+
display: "flex",
|
|
597
|
+
flexDirection: "column",
|
|
598
|
+
alignItems: "center",
|
|
599
|
+
gap: 18,
|
|
600
|
+
color: "var(--psmi-chrome-fg, #fff)",
|
|
601
|
+
zIndex: 3
|
|
602
|
+
},
|
|
436
603
|
children: [
|
|
437
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
438
|
-
|
|
604
|
+
typeof meta.likeCount === "number" ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
605
|
+
RailStat,
|
|
606
|
+
{
|
|
607
|
+
iconName: "lucide/heart",
|
|
608
|
+
count: meta.likeCount,
|
|
609
|
+
label: "likes",
|
|
610
|
+
onPress: open
|
|
611
|
+
}
|
|
612
|
+
) : null,
|
|
613
|
+
typeof meta.commentCount === "number" ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
614
|
+
RailStat,
|
|
615
|
+
{
|
|
616
|
+
iconName: "lucide/message-circle",
|
|
617
|
+
count: meta.commentCount,
|
|
618
|
+
label: "comments",
|
|
619
|
+
onPress: open
|
|
620
|
+
}
|
|
621
|
+
) : null,
|
|
622
|
+
typeof meta.viewCount === "number" ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
623
|
+
RailStat,
|
|
624
|
+
{
|
|
625
|
+
iconName: "lucide/eye",
|
|
626
|
+
count: meta.viewCount,
|
|
627
|
+
label: "views",
|
|
628
|
+
onPress: open
|
|
629
|
+
}
|
|
630
|
+
) : null,
|
|
631
|
+
meta.href ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
632
|
+
"a",
|
|
633
|
+
{
|
|
634
|
+
href: meta.href,
|
|
635
|
+
target: "_blank",
|
|
636
|
+
rel: "noopener noreferrer",
|
|
637
|
+
"aria-label": "Open in Instagram",
|
|
638
|
+
onClick: (e) => e.stopPropagation(),
|
|
639
|
+
style: {
|
|
640
|
+
display: "flex",
|
|
641
|
+
flexDirection: "column",
|
|
642
|
+
alignItems: "center",
|
|
643
|
+
gap: 5,
|
|
644
|
+
padding: 0,
|
|
645
|
+
border: "none",
|
|
646
|
+
background: "transparent",
|
|
647
|
+
color: "inherit",
|
|
648
|
+
cursor: "pointer",
|
|
649
|
+
font: "inherit",
|
|
650
|
+
textDecoration: "none"
|
|
651
|
+
},
|
|
652
|
+
children: [
|
|
653
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
654
|
+
"span",
|
|
655
|
+
{
|
|
656
|
+
style: {
|
|
657
|
+
width: 46,
|
|
658
|
+
height: 46,
|
|
659
|
+
borderRadius: "50%",
|
|
660
|
+
background: "var(--psmi-chrome-bg, rgba(255,255,255,0.14))",
|
|
661
|
+
backdropFilter: "blur(6px)",
|
|
662
|
+
WebkitBackdropFilter: "blur(6px)",
|
|
663
|
+
display: "flex",
|
|
664
|
+
alignItems: "center",
|
|
665
|
+
justifyContent: "center"
|
|
666
|
+
},
|
|
667
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
668
|
+
DynamicIcon,
|
|
669
|
+
{
|
|
670
|
+
name: "lucide/external-link",
|
|
671
|
+
size: 22,
|
|
672
|
+
"aria-hidden": "true"
|
|
673
|
+
}
|
|
674
|
+
)
|
|
675
|
+
}
|
|
676
|
+
),
|
|
677
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", style: { fontSize: 11, fontWeight: 600 }, children: "Instagram" })
|
|
678
|
+
]
|
|
679
|
+
}
|
|
680
|
+
) : null
|
|
439
681
|
]
|
|
440
682
|
}
|
|
441
683
|
);
|
|
442
684
|
}
|
|
685
|
+
function InstagramFeedGrid({
|
|
686
|
+
mediaItems,
|
|
687
|
+
gridClassName,
|
|
688
|
+
itemClassName,
|
|
689
|
+
imageClassName,
|
|
690
|
+
optixFlowConfig
|
|
691
|
+
}) {
|
|
692
|
+
const { open } = mediaImmersive.useImmersiveFeed();
|
|
693
|
+
const posterImgProps = React.useMemo(() => {
|
|
694
|
+
const next = {};
|
|
695
|
+
if (imageClassName) next.className = imageClassName;
|
|
696
|
+
if (optixFlowConfig) next.optixFlowConfig = optixFlowConfig;
|
|
697
|
+
return Object.keys(next).length > 0 ? next : void 0;
|
|
698
|
+
}, [imageClassName, optixFlowConfig]);
|
|
699
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
700
|
+
"div",
|
|
701
|
+
{
|
|
702
|
+
className: cn(
|
|
703
|
+
"grid grid-cols-2 gap-3 sm:grid-cols-3 lg:grid-cols-4",
|
|
704
|
+
gridClassName
|
|
705
|
+
),
|
|
706
|
+
children: mediaItems.map((mediaItem) => {
|
|
707
|
+
const likeCount = mediaItem.meta?.likeCount;
|
|
708
|
+
return (
|
|
709
|
+
// The wrapper owns the tile's outer chrome (subtle boundary shadow,
|
|
710
|
+
// matching radius) and the block's own caption overlay — the card's
|
|
711
|
+
// built-in caption is hidden so 1- and 2-line captions can share a
|
|
712
|
+
// fixed-height, top-aligned text box (annotated refinements #2–#4).
|
|
713
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: itemClassName, style: TILE_WRAPPER_STYLE, children: [
|
|
714
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
715
|
+
mediaImmersive.ThumbnailCard,
|
|
716
|
+
{
|
|
717
|
+
item: mediaItem,
|
|
718
|
+
onOpen: open,
|
|
719
|
+
size: IG_TILE_WIDTH,
|
|
720
|
+
style: IG_TILE_STYLE,
|
|
721
|
+
elevated: false,
|
|
722
|
+
hideCaption: true,
|
|
723
|
+
hideProgressHint: true,
|
|
724
|
+
showDuration: false,
|
|
725
|
+
glyphMode: "hover",
|
|
726
|
+
posterImgProps,
|
|
727
|
+
badgeSlot: typeof likeCount === "number" ? likeBadge(likeCount) : void 0
|
|
728
|
+
}
|
|
729
|
+
),
|
|
730
|
+
mediaItem.title ? (
|
|
731
|
+
// aria-hidden: the card already announces the post title; this
|
|
732
|
+
// overlay is purely visual. pointer-events pass through to the card.
|
|
733
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { "aria-hidden": "true", style: CAPTION_SCRIM_STYLE, children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: CAPTION_TEXT_STYLE, children: mediaItem.title }) })
|
|
734
|
+
) : null
|
|
735
|
+
] }, mediaItem.id)
|
|
736
|
+
);
|
|
737
|
+
})
|
|
738
|
+
}
|
|
739
|
+
);
|
|
740
|
+
}
|
|
443
741
|
function InstagramPostGrid({
|
|
444
742
|
sectionId = "instagram-post-grid",
|
|
445
743
|
heading,
|
|
@@ -447,118 +745,32 @@ function InstagramPostGrid({
|
|
|
447
745
|
items,
|
|
448
746
|
itemsSlot,
|
|
449
747
|
className,
|
|
450
|
-
containerClassName,
|
|
748
|
+
containerClassName = "px-6 sm:px-6 md:px-8 lg:px-8",
|
|
749
|
+
spacing = "py-12 md:py-32",
|
|
451
750
|
headerClassName,
|
|
452
751
|
headingClassName,
|
|
453
752
|
subheadingClassName,
|
|
454
753
|
gridClassName,
|
|
455
754
|
itemClassName,
|
|
456
755
|
imageClassName,
|
|
756
|
+
optixFlowConfig,
|
|
457
757
|
background,
|
|
458
|
-
spacing,
|
|
459
758
|
pattern,
|
|
460
759
|
patternOpacity,
|
|
461
|
-
patternClassName
|
|
462
|
-
optixFlowConfig
|
|
760
|
+
patternClassName
|
|
463
761
|
}) {
|
|
464
|
-
const
|
|
465
|
-
if (itemsSlot) return
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
const
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
pressable.Pressable,
|
|
473
|
-
{
|
|
474
|
-
href: item.href,
|
|
475
|
-
"aria-label": typeof item.caption === "string" && item.caption.length > 0 ? item.caption : "View Instagram post",
|
|
476
|
-
className: cn(
|
|
477
|
-
"group relative block aspect-square overflow-hidden rounded-md bg-muted",
|
|
478
|
-
item.className,
|
|
479
|
-
itemClassName
|
|
480
|
-
),
|
|
481
|
-
children: [
|
|
482
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
483
|
-
img.Img,
|
|
484
|
-
{
|
|
485
|
-
src: item.image,
|
|
486
|
-
alt: altText,
|
|
487
|
-
loading: "lazy",
|
|
488
|
-
className: cn(
|
|
489
|
-
"h-full w-full object-cover object-center transition-transform duration-300 group-hover:scale-105 motion-reduce:transform-none motion-reduce:transition-none",
|
|
490
|
-
imageClassName
|
|
491
|
-
),
|
|
492
|
-
optixFlowConfig
|
|
493
|
-
}
|
|
494
|
-
),
|
|
495
|
-
showVideo && /* @__PURE__ */ jsxRuntime.jsx(
|
|
496
|
-
video.Video,
|
|
497
|
-
{
|
|
498
|
-
src: item.videoUrl,
|
|
499
|
-
poster: item.image,
|
|
500
|
-
controls: false,
|
|
501
|
-
muted: true,
|
|
502
|
-
loop: true,
|
|
503
|
-
playsInline: true,
|
|
504
|
-
preload: "metadata",
|
|
505
|
-
onMouseEnter: (e) => {
|
|
506
|
-
void e.currentTarget.play().catch(() => void 0);
|
|
507
|
-
},
|
|
508
|
-
onMouseLeave: (e) => {
|
|
509
|
-
e.currentTarget.pause();
|
|
510
|
-
e.currentTarget.currentTime = 0;
|
|
511
|
-
},
|
|
512
|
-
className: cn(
|
|
513
|
-
"absolute inset-0 h-full w-full object-cover object-center opacity-0 transition-opacity duration-300 group-hover:opacity-100 motion-reduce:transition-none",
|
|
514
|
-
imageClassName
|
|
515
|
-
)
|
|
516
|
-
}
|
|
517
|
-
),
|
|
518
|
-
item.isVideo && /* @__PURE__ */ jsxRuntime.jsx(
|
|
519
|
-
"span",
|
|
520
|
-
{
|
|
521
|
-
className: "absolute right-2 top-2 text-white drop-shadow-md",
|
|
522
|
-
"aria-hidden": "true",
|
|
523
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(DynamicIcon, { name: "lucide/play", size: 18 })
|
|
524
|
-
}
|
|
525
|
-
),
|
|
526
|
-
(item.caption || item.date || typeof item.likeCount === "number" || typeof item.commentCount === "number" || typeof item.viewCount === "number") && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pointer-events-none absolute inset-x-0 bottom-0 flex flex-col gap-1 bg-linear-to-t from-black/70 to-transparent p-3 text-left text-white opacity-0 transition-opacity duration-300 group-hover:opacity-100 motion-reduce:transition-none", children: [
|
|
527
|
-
item.caption && (typeof item.caption === "string" ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "line-clamp-2 text-xs leading-snug wrap-break-word", children: item.caption }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "line-clamp-2 text-xs leading-snug", children: item.caption })),
|
|
528
|
-
(typeof item.likeCount === "number" || typeof item.commentCount === "number" || typeof item.viewCount === "number" || item.date) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 text-xs font-medium", children: [
|
|
529
|
-
typeof item.likeCount === "number" && /* @__PURE__ */ jsxRuntime.jsx(
|
|
530
|
-
EngagementBadge,
|
|
531
|
-
{
|
|
532
|
-
iconName: "lucide/heart",
|
|
533
|
-
count: item.likeCount,
|
|
534
|
-
label: "likes"
|
|
535
|
-
}
|
|
536
|
-
),
|
|
537
|
-
typeof item.commentCount === "number" && /* @__PURE__ */ jsxRuntime.jsx(
|
|
538
|
-
EngagementBadge,
|
|
539
|
-
{
|
|
540
|
-
iconName: "lucide/message-circle",
|
|
541
|
-
count: item.commentCount,
|
|
542
|
-
label: "comments"
|
|
543
|
-
}
|
|
544
|
-
),
|
|
545
|
-
typeof item.viewCount === "number" && /* @__PURE__ */ jsxRuntime.jsx(
|
|
546
|
-
EngagementBadge,
|
|
547
|
-
{
|
|
548
|
-
iconName: "lucide/eye",
|
|
549
|
-
count: item.viewCount,
|
|
550
|
-
label: "views"
|
|
551
|
-
}
|
|
552
|
-
),
|
|
553
|
-
item.date && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ml-auto opacity-80", children: item.date })
|
|
554
|
-
] })
|
|
555
|
-
] })
|
|
556
|
-
]
|
|
557
|
-
},
|
|
558
|
-
item.id
|
|
762
|
+
const mediaItems = React.useMemo(() => {
|
|
763
|
+
if (itemsSlot || !items) return [];
|
|
764
|
+
const withImage = items.filter((item) => Boolean(item.image));
|
|
765
|
+
if (process.env.NODE_ENV !== "production" && withImage.length < items.length) {
|
|
766
|
+
const skipped = items.filter((item) => !item.image);
|
|
767
|
+
const skippedIds = skipped.map((item) => item.id).join(", ");
|
|
768
|
+
console.warn(
|
|
769
|
+
`[instagram-post-grid] skipped ${skipped.length} item(s) without a resolvable image: ${skippedIds}`
|
|
559
770
|
);
|
|
560
|
-
}
|
|
561
|
-
|
|
771
|
+
}
|
|
772
|
+
return withImage.map(toMediaItem);
|
|
773
|
+
}, [items, itemsSlot]);
|
|
562
774
|
if (!itemsSlot && (!items || items.length === 0)) {
|
|
563
775
|
return null;
|
|
564
776
|
}
|
|
@@ -596,16 +808,34 @@ function InstagramPostGrid({
|
|
|
596
808
|
}
|
|
597
809
|
) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: subheadingClassName, children: subheading }))
|
|
598
810
|
] }),
|
|
599
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
811
|
+
itemsSlot ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
600
812
|
"div",
|
|
601
813
|
{
|
|
602
814
|
className: cn(
|
|
603
|
-
"grid grid-cols-2 gap-
|
|
815
|
+
"grid grid-cols-2 gap-3 sm:grid-cols-3 lg:grid-cols-4",
|
|
604
816
|
gridClassName
|
|
605
817
|
),
|
|
606
|
-
children:
|
|
818
|
+
children: itemsSlot
|
|
607
819
|
}
|
|
608
|
-
)
|
|
820
|
+
) : /* @__PURE__ */ jsxRuntime.jsxs(mediaImmersive.ImmersiveFeedProvider, { items: mediaItems, actions: VIEWER_ACTIONS, children: [
|
|
821
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
822
|
+
InstagramFeedGrid,
|
|
823
|
+
{
|
|
824
|
+
mediaItems,
|
|
825
|
+
gridClassName,
|
|
826
|
+
itemClassName,
|
|
827
|
+
imageClassName,
|
|
828
|
+
optixFlowConfig
|
|
829
|
+
}
|
|
830
|
+
),
|
|
831
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
832
|
+
mediaImmersive.ImmersiveViewer,
|
|
833
|
+
{
|
|
834
|
+
ariaLabel: "Instagram post viewer",
|
|
835
|
+
renderActions: ({ item }) => /* @__PURE__ */ jsxRuntime.jsx(InstagramViewerRail, { item })
|
|
836
|
+
}
|
|
837
|
+
)
|
|
838
|
+
] })
|
|
609
839
|
]
|
|
610
840
|
}
|
|
611
841
|
);
|
|
@@ -31,7 +31,15 @@ interface InstagramPostItem {
|
|
|
31
31
|
*/
|
|
32
32
|
imageAlt?: string;
|
|
33
33
|
/**
|
|
34
|
-
* Post caption (truncated for display)
|
|
34
|
+
* Post caption (truncated for display).
|
|
35
|
+
*
|
|
36
|
+
* NOTE: For immersive rendering the caption must be a **string** — the
|
|
37
|
+
* library's `MediaItem.title`/`caption` are string-typed, so a caption is
|
|
38
|
+
* flowed through as text. A rich `ReactNode` caption (hand-authored markup,
|
|
39
|
+
* only ever used by the legacy pre-immersive block) cannot be threaded into
|
|
40
|
+
* the immersive card/viewer; when supplied it is dropped and the tile/viewer
|
|
41
|
+
* title falls back to `imageAlt` (then `"Instagram post"`). Hydrated feeds
|
|
42
|
+
* always send plain strings, so this only affects hand-authored usage.
|
|
35
43
|
*/
|
|
36
44
|
caption?: React.ReactNode;
|
|
37
45
|
/**
|
|
@@ -140,17 +148,32 @@ interface InstagramPostGridProps {
|
|
|
140
148
|
sectionId?: string;
|
|
141
149
|
}
|
|
142
150
|
/**
|
|
143
|
-
* InstagramPostGrid displays a responsive
|
|
151
|
+
* InstagramPostGrid displays a website's Instagram feed as a responsive grid of
|
|
152
|
+
* vertical (9:16) tiles that open a fullscreen, swipeable immersive viewer.
|
|
144
153
|
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
*
|
|
154
|
+
* Composition is powered by `@page-speed/media-immersive`: each post maps to a
|
|
155
|
+
* `MediaItem` (photos are first-class images, reels are videos), tiles are
|
|
156
|
+
* `ThumbnailCard`s, and tapping one opens `ImmersiveViewer` — a TikTok/Reels-
|
|
157
|
+
* style pager. Per the annotated design, each tile shows a like-count pill
|
|
158
|
+
* top-left (only when a numeric like count exists — never fabricated), no
|
|
159
|
+
* duration timestamp, and a center glyph on hover only (a play triangle for
|
|
160
|
+
* reels, an expand icon for photos). The fullscreen viewer carries the caption
|
|
161
|
+
* plus a read-only engagement rail (likes/comments/views) and an explicit
|
|
162
|
+
* "Open in Instagram" egress rendered as a real `<a target="_blank"
|
|
163
|
+
* rel="noopener noreferrer">` (a crawlable, affordance-correct link — not a
|
|
164
|
+
* scripted `window.open` button); the old whole-tile link-out is REPLACED by
|
|
165
|
+
* opening the viewer, with the permalink egress living in the viewer.
|
|
151
166
|
*
|
|
152
|
-
*
|
|
153
|
-
*
|
|
167
|
+
* Consumer image concerns are honored: `imageClassName` and `optixFlowConfig`
|
|
168
|
+
* are forwarded onto each tile's poster image via the library's
|
|
169
|
+
* `posterImgProps` passthrough.
|
|
170
|
+
*
|
|
171
|
+
* Captions should be plain strings when used with immersive rendering; a rich
|
|
172
|
+
* `ReactNode` caption falls back to `imageAlt` (see the `caption` field doc).
|
|
173
|
+
*
|
|
174
|
+
* Posts without an image are skipped; an empty or missing `items` array renders
|
|
175
|
+
* nothing. Data is hydrated from the toastability Instagram feed — media URLs
|
|
176
|
+
* are the re-hosted MediaRecord CDN URLs, never expiring Instagram CDN URLs.
|
|
154
177
|
*
|
|
155
178
|
* @example
|
|
156
179
|
* ```tsx
|
|
@@ -169,6 +192,6 @@ interface InstagramPostGridProps {
|
|
|
169
192
|
* />
|
|
170
193
|
* ```
|
|
171
194
|
*/
|
|
172
|
-
declare function InstagramPostGrid({ sectionId, heading, subheading, items, itemsSlot, className, containerClassName, headerClassName, headingClassName, subheadingClassName, gridClassName, itemClassName, imageClassName,
|
|
195
|
+
declare function InstagramPostGrid({ sectionId, heading, subheading, items, itemsSlot, className, containerClassName, spacing, headerClassName, headingClassName, subheadingClassName, gridClassName, itemClassName, imageClassName, optixFlowConfig, background, pattern, patternOpacity, patternClassName, }: InstagramPostGridProps): React.JSX.Element | null;
|
|
173
196
|
|
|
174
197
|
export { InstagramPostGrid, type InstagramPostGridProps, type InstagramPostItem };
|
|
@@ -31,7 +31,15 @@ interface InstagramPostItem {
|
|
|
31
31
|
*/
|
|
32
32
|
imageAlt?: string;
|
|
33
33
|
/**
|
|
34
|
-
* Post caption (truncated for display)
|
|
34
|
+
* Post caption (truncated for display).
|
|
35
|
+
*
|
|
36
|
+
* NOTE: For immersive rendering the caption must be a **string** — the
|
|
37
|
+
* library's `MediaItem.title`/`caption` are string-typed, so a caption is
|
|
38
|
+
* flowed through as text. A rich `ReactNode` caption (hand-authored markup,
|
|
39
|
+
* only ever used by the legacy pre-immersive block) cannot be threaded into
|
|
40
|
+
* the immersive card/viewer; when supplied it is dropped and the tile/viewer
|
|
41
|
+
* title falls back to `imageAlt` (then `"Instagram post"`). Hydrated feeds
|
|
42
|
+
* always send plain strings, so this only affects hand-authored usage.
|
|
35
43
|
*/
|
|
36
44
|
caption?: React.ReactNode;
|
|
37
45
|
/**
|
|
@@ -140,17 +148,32 @@ interface InstagramPostGridProps {
|
|
|
140
148
|
sectionId?: string;
|
|
141
149
|
}
|
|
142
150
|
/**
|
|
143
|
-
* InstagramPostGrid displays a responsive
|
|
151
|
+
* InstagramPostGrid displays a website's Instagram feed as a responsive grid of
|
|
152
|
+
* vertical (9:16) tiles that open a fullscreen, swipeable immersive viewer.
|
|
144
153
|
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
*
|
|
154
|
+
* Composition is powered by `@page-speed/media-immersive`: each post maps to a
|
|
155
|
+
* `MediaItem` (photos are first-class images, reels are videos), tiles are
|
|
156
|
+
* `ThumbnailCard`s, and tapping one opens `ImmersiveViewer` — a TikTok/Reels-
|
|
157
|
+
* style pager. Per the annotated design, each tile shows a like-count pill
|
|
158
|
+
* top-left (only when a numeric like count exists — never fabricated), no
|
|
159
|
+
* duration timestamp, and a center glyph on hover only (a play triangle for
|
|
160
|
+
* reels, an expand icon for photos). The fullscreen viewer carries the caption
|
|
161
|
+
* plus a read-only engagement rail (likes/comments/views) and an explicit
|
|
162
|
+
* "Open in Instagram" egress rendered as a real `<a target="_blank"
|
|
163
|
+
* rel="noopener noreferrer">` (a crawlable, affordance-correct link — not a
|
|
164
|
+
* scripted `window.open` button); the old whole-tile link-out is REPLACED by
|
|
165
|
+
* opening the viewer, with the permalink egress living in the viewer.
|
|
151
166
|
*
|
|
152
|
-
*
|
|
153
|
-
*
|
|
167
|
+
* Consumer image concerns are honored: `imageClassName` and `optixFlowConfig`
|
|
168
|
+
* are forwarded onto each tile's poster image via the library's
|
|
169
|
+
* `posterImgProps` passthrough.
|
|
170
|
+
*
|
|
171
|
+
* Captions should be plain strings when used with immersive rendering; a rich
|
|
172
|
+
* `ReactNode` caption falls back to `imageAlt` (see the `caption` field doc).
|
|
173
|
+
*
|
|
174
|
+
* Posts without an image are skipped; an empty or missing `items` array renders
|
|
175
|
+
* nothing. Data is hydrated from the toastability Instagram feed — media URLs
|
|
176
|
+
* are the re-hosted MediaRecord CDN URLs, never expiring Instagram CDN URLs.
|
|
154
177
|
*
|
|
155
178
|
* @example
|
|
156
179
|
* ```tsx
|
|
@@ -169,6 +192,6 @@ interface InstagramPostGridProps {
|
|
|
169
192
|
* />
|
|
170
193
|
* ```
|
|
171
194
|
*/
|
|
172
|
-
declare function InstagramPostGrid({ sectionId, heading, subheading, items, itemsSlot, className, containerClassName, headerClassName, headingClassName, subheadingClassName, gridClassName, itemClassName, imageClassName,
|
|
195
|
+
declare function InstagramPostGrid({ sectionId, heading, subheading, items, itemsSlot, className, containerClassName, spacing, headerClassName, headingClassName, subheadingClassName, gridClassName, itemClassName, imageClassName, optixFlowConfig, background, pattern, patternOpacity, patternClassName, }: InstagramPostGridProps): React.JSX.Element | null;
|
|
173
196
|
|
|
174
197
|
export { InstagramPostGrid, type InstagramPostGridProps, type InstagramPostItem };
|