@opensite/ui 3.10.0 → 3.11.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.
@@ -1,11 +1,9 @@
1
1
  "use client";
2
2
  import * as React from 'react';
3
3
  import React__default, { useMemo } from 'react';
4
+ import { ImmersiveFeedProvider, ImmersiveViewer, useImmersiveFeed, ThumbnailCard } from '@page-speed/media-immersive';
4
5
  import { clsx } from 'clsx';
5
6
  import { twMerge } from 'tailwind-merge';
6
- import { Img } from '@page-speed/img';
7
- import { Video } from '@page-speed/video';
8
- import { Pressable } from '@page-speed/pressable';
9
7
  import { Icon } from '@page-speed/icon';
10
8
  import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
11
9
 
@@ -402,23 +400,276 @@ var Section = React__default.forwardRef(
402
400
  }
403
401
  );
404
402
  Section.displayName = "Section";
405
- function EngagementBadge({
403
+ var IG_TILE_WIDTH = 320;
404
+ var IG_TILE_STYLE = { width: "100%" };
405
+ var TITLE_MAX = 90;
406
+ function truncate(text, max = TITLE_MAX) {
407
+ const trimmed = text.trim();
408
+ if (trimmed.length <= max) return trimmed;
409
+ const slice = trimmed.slice(0, max);
410
+ const lastSpace = slice.lastIndexOf(" ");
411
+ const head = lastSpace > max * 0.6 ? slice.slice(0, lastSpace) : slice;
412
+ return `${head.trimEnd()}\u2026`;
413
+ }
414
+ function captionToString(caption) {
415
+ return typeof caption === "string" ? caption : "";
416
+ }
417
+ function toMediaItem(item) {
418
+ const isVideo = Boolean(item.isVideo && item.videoUrl);
419
+ const fullCaption = captionToString(item.caption);
420
+ const title = truncate(fullCaption) || item.imageAlt || "Instagram post";
421
+ const meta = {
422
+ href: item.href,
423
+ likeCount: item.likeCount,
424
+ commentCount: item.commentCount,
425
+ viewCount: item.viewCount,
426
+ date: item.date
427
+ };
428
+ return {
429
+ id: item.id,
430
+ type: isVideo ? "video" : "image",
431
+ poster: item.image,
432
+ // Video source only when the post is a real video (image posts ignore it).
433
+ src: isVideo ? item.videoUrl : void 0,
434
+ title,
435
+ caption: fullCaption || void 0,
436
+ kind: "Instagram",
437
+ meta
438
+ };
439
+ }
440
+ function openPermalink(item) {
441
+ if (typeof window === "undefined") return;
442
+ const href = item.meta?.href;
443
+ if (!href) return;
444
+ window.open(href, "_blank", "noopener,noreferrer");
445
+ }
446
+ function likeBadge(likeCount) {
447
+ return /* @__PURE__ */ jsxs(
448
+ "span",
449
+ {
450
+ "aria-label": `${likeCount.toLocaleString()} likes`,
451
+ style: {
452
+ display: "inline-flex",
453
+ alignItems: "center",
454
+ gap: 4,
455
+ padding: "3px 8px",
456
+ borderRadius: 999,
457
+ background: "rgba(8,12,24,0.65)",
458
+ backdropFilter: "blur(6px)",
459
+ WebkitBackdropFilter: "blur(6px)",
460
+ color: "#fff",
461
+ fontSize: 11,
462
+ fontWeight: 700,
463
+ lineHeight: 1
464
+ },
465
+ children: [
466
+ /* @__PURE__ */ jsx(DynamicIcon, { name: "lucide/heart", size: 13, "aria-hidden": "true" }),
467
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: likeCount.toLocaleString() })
468
+ ]
469
+ }
470
+ );
471
+ }
472
+ var VIEWER_ACTIONS = [
473
+ {
474
+ id: "open-in-instagram",
475
+ icon: /* @__PURE__ */ jsx(DynamicIcon, { name: "lucide/external-link", size: 22, "aria-hidden": "true" }),
476
+ label: "Instagram",
477
+ ariaLabel: "Open in Instagram",
478
+ onPress: (item) => openPermalink(item)
479
+ }
480
+ ];
481
+ function RailStat({
406
482
  iconName,
407
483
  count,
408
- label
484
+ label,
485
+ onPress
409
486
  }) {
410
487
  return /* @__PURE__ */ jsxs(
411
- "span",
488
+ "button",
412
489
  {
413
- className: "inline-flex items-center gap-1",
490
+ type: "button",
414
491
  "aria-label": `${count.toLocaleString()} ${label}`,
492
+ onClick: (e) => {
493
+ e.stopPropagation();
494
+ onPress();
495
+ },
496
+ style: {
497
+ display: "flex",
498
+ flexDirection: "column",
499
+ alignItems: "center",
500
+ gap: 5,
501
+ padding: 0,
502
+ border: "none",
503
+ background: "transparent",
504
+ color: "inherit",
505
+ cursor: "pointer",
506
+ font: "inherit"
507
+ },
508
+ children: [
509
+ /* @__PURE__ */ jsx(
510
+ "span",
511
+ {
512
+ style: {
513
+ width: 46,
514
+ height: 46,
515
+ borderRadius: "50%",
516
+ background: "var(--psmi-chrome-bg, rgba(255,255,255,0.14))",
517
+ backdropFilter: "blur(6px)",
518
+ WebkitBackdropFilter: "blur(6px)",
519
+ display: "flex",
520
+ alignItems: "center",
521
+ justifyContent: "center"
522
+ },
523
+ children: /* @__PURE__ */ jsx(DynamicIcon, { name: iconName, size: 22, "aria-hidden": "true" })
524
+ }
525
+ ),
526
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", style: { fontSize: 11, fontWeight: 600 }, children: count.toLocaleString() })
527
+ ]
528
+ }
529
+ );
530
+ }
531
+ function InstagramViewerRail({ item }) {
532
+ const meta = item.meta ?? {};
533
+ const open = () => openPermalink(item);
534
+ return /* @__PURE__ */ jsxs(
535
+ "div",
536
+ {
537
+ style: {
538
+ position: "absolute",
539
+ right: 11,
540
+ bottom: 135,
541
+ display: "flex",
542
+ flexDirection: "column",
543
+ alignItems: "center",
544
+ gap: 18,
545
+ color: "var(--psmi-chrome-fg, #fff)",
546
+ zIndex: 3
547
+ },
415
548
  children: [
416
- /* @__PURE__ */ jsx(DynamicIcon, { name: iconName, size: 16, "aria-hidden": "true" }),
417
- /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: count.toLocaleString() })
549
+ typeof meta.likeCount === "number" ? /* @__PURE__ */ jsx(
550
+ RailStat,
551
+ {
552
+ iconName: "lucide/heart",
553
+ count: meta.likeCount,
554
+ label: "likes",
555
+ onPress: open
556
+ }
557
+ ) : null,
558
+ typeof meta.commentCount === "number" ? /* @__PURE__ */ jsx(
559
+ RailStat,
560
+ {
561
+ iconName: "lucide/message-circle",
562
+ count: meta.commentCount,
563
+ label: "comments",
564
+ onPress: open
565
+ }
566
+ ) : null,
567
+ typeof meta.viewCount === "number" ? /* @__PURE__ */ jsx(
568
+ RailStat,
569
+ {
570
+ iconName: "lucide/eye",
571
+ count: meta.viewCount,
572
+ label: "views",
573
+ onPress: open
574
+ }
575
+ ) : null,
576
+ meta.href ? /* @__PURE__ */ jsxs(
577
+ "a",
578
+ {
579
+ href: meta.href,
580
+ target: "_blank",
581
+ rel: "noopener noreferrer",
582
+ "aria-label": "Open in Instagram",
583
+ onClick: (e) => e.stopPropagation(),
584
+ style: {
585
+ display: "flex",
586
+ flexDirection: "column",
587
+ alignItems: "center",
588
+ gap: 5,
589
+ padding: 0,
590
+ border: "none",
591
+ background: "transparent",
592
+ color: "inherit",
593
+ cursor: "pointer",
594
+ font: "inherit",
595
+ textDecoration: "none"
596
+ },
597
+ children: [
598
+ /* @__PURE__ */ jsx(
599
+ "span",
600
+ {
601
+ style: {
602
+ width: 46,
603
+ height: 46,
604
+ borderRadius: "50%",
605
+ background: "var(--psmi-chrome-bg, rgba(255,255,255,0.14))",
606
+ backdropFilter: "blur(6px)",
607
+ WebkitBackdropFilter: "blur(6px)",
608
+ display: "flex",
609
+ alignItems: "center",
610
+ justifyContent: "center"
611
+ },
612
+ children: /* @__PURE__ */ jsx(
613
+ DynamicIcon,
614
+ {
615
+ name: "lucide/external-link",
616
+ size: 22,
617
+ "aria-hidden": "true"
618
+ }
619
+ )
620
+ }
621
+ ),
622
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", style: { fontSize: 11, fontWeight: 600 }, children: "Instagram" })
623
+ ]
624
+ }
625
+ ) : null
418
626
  ]
419
627
  }
420
628
  );
421
629
  }
630
+ function InstagramFeedGrid({
631
+ mediaItems,
632
+ gridClassName,
633
+ itemClassName,
634
+ imageClassName,
635
+ optixFlowConfig
636
+ }) {
637
+ const { open } = useImmersiveFeed();
638
+ const posterImgProps = useMemo(() => {
639
+ const next = {};
640
+ if (imageClassName) next.className = imageClassName;
641
+ if (optixFlowConfig) next.optixFlowConfig = optixFlowConfig;
642
+ return Object.keys(next).length > 0 ? next : void 0;
643
+ }, [imageClassName, optixFlowConfig]);
644
+ return /* @__PURE__ */ jsx(
645
+ "div",
646
+ {
647
+ className: cn(
648
+ "grid grid-cols-2 gap-2 sm:grid-cols-3 lg:grid-cols-4",
649
+ gridClassName
650
+ ),
651
+ children: mediaItems.map((mediaItem) => {
652
+ const likeCount = mediaItem.meta?.likeCount;
653
+ return /* @__PURE__ */ jsx(
654
+ ThumbnailCard,
655
+ {
656
+ item: mediaItem,
657
+ onOpen: open,
658
+ size: IG_TILE_WIDTH,
659
+ style: IG_TILE_STYLE,
660
+ className: itemClassName,
661
+ elevated: false,
662
+ showDuration: false,
663
+ glyphMode: "hover",
664
+ posterImgProps,
665
+ badgeSlot: typeof likeCount === "number" ? likeBadge(likeCount) : void 0
666
+ },
667
+ mediaItem.id
668
+ );
669
+ })
670
+ }
671
+ );
672
+ }
422
673
  function InstagramPostGrid({
423
674
  sectionId = "instagram-post-grid",
424
675
  heading,
@@ -433,111 +684,17 @@ function InstagramPostGrid({
433
684
  gridClassName,
434
685
  itemClassName,
435
686
  imageClassName,
687
+ optixFlowConfig,
436
688
  background,
437
689
  spacing,
438
690
  pattern,
439
691
  patternOpacity,
440
- patternClassName,
441
- optixFlowConfig
692
+ patternClassName
442
693
  }) {
443
- const tiles = useMemo(() => {
444
- if (itemsSlot) return itemsSlot;
445
- if (!items || items.length === 0) return null;
446
- const visibleItems = items.filter((item) => Boolean(item.image));
447
- return visibleItems.map((item) => {
448
- const altText = item.imageAlt || (typeof item.caption === "string" ? item.caption : "") || "Instagram post";
449
- const showVideo = Boolean(item.isVideo && item.videoUrl);
450
- return /* @__PURE__ */ jsxs(
451
- Pressable,
452
- {
453
- href: item.href,
454
- "aria-label": typeof item.caption === "string" && item.caption.length > 0 ? item.caption : "View Instagram post",
455
- className: cn(
456
- "group relative block aspect-square overflow-hidden rounded-md bg-muted",
457
- item.className,
458
- itemClassName
459
- ),
460
- children: [
461
- /* @__PURE__ */ jsx(
462
- Img,
463
- {
464
- src: item.image,
465
- alt: altText,
466
- loading: "lazy",
467
- className: cn(
468
- "h-full w-full object-cover object-center transition-transform duration-300 group-hover:scale-105 motion-reduce:transform-none motion-reduce:transition-none",
469
- imageClassName
470
- ),
471
- optixFlowConfig
472
- }
473
- ),
474
- showVideo && /* @__PURE__ */ jsx(
475
- Video,
476
- {
477
- src: item.videoUrl,
478
- poster: item.image,
479
- controls: false,
480
- muted: true,
481
- loop: true,
482
- playsInline: true,
483
- preload: "metadata",
484
- onMouseEnter: (e) => {
485
- void e.currentTarget.play().catch(() => void 0);
486
- },
487
- onMouseLeave: (e) => {
488
- e.currentTarget.pause();
489
- e.currentTarget.currentTime = 0;
490
- },
491
- className: cn(
492
- "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",
493
- imageClassName
494
- )
495
- }
496
- ),
497
- item.isVideo && /* @__PURE__ */ jsx(
498
- "span",
499
- {
500
- className: "absolute right-2 top-2 text-white drop-shadow-md",
501
- "aria-hidden": "true",
502
- children: /* @__PURE__ */ jsx(DynamicIcon, { name: "lucide/play", size: 18 })
503
- }
504
- ),
505
- (item.caption || item.date || typeof item.likeCount === "number" || typeof item.commentCount === "number" || typeof item.viewCount === "number") && /* @__PURE__ */ 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: [
506
- item.caption && (typeof item.caption === "string" ? /* @__PURE__ */ jsx("p", { className: "line-clamp-2 text-xs leading-snug wrap-break-word", children: item.caption }) : /* @__PURE__ */ jsx("div", { className: "line-clamp-2 text-xs leading-snug", children: item.caption })),
507
- (typeof item.likeCount === "number" || typeof item.commentCount === "number" || typeof item.viewCount === "number" || item.date) && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 text-xs font-medium", children: [
508
- typeof item.likeCount === "number" && /* @__PURE__ */ jsx(
509
- EngagementBadge,
510
- {
511
- iconName: "lucide/heart",
512
- count: item.likeCount,
513
- label: "likes"
514
- }
515
- ),
516
- typeof item.commentCount === "number" && /* @__PURE__ */ jsx(
517
- EngagementBadge,
518
- {
519
- iconName: "lucide/message-circle",
520
- count: item.commentCount,
521
- label: "comments"
522
- }
523
- ),
524
- typeof item.viewCount === "number" && /* @__PURE__ */ jsx(
525
- EngagementBadge,
526
- {
527
- iconName: "lucide/eye",
528
- count: item.viewCount,
529
- label: "views"
530
- }
531
- ),
532
- item.date && /* @__PURE__ */ jsx("span", { className: "ml-auto opacity-80", children: item.date })
533
- ] })
534
- ] })
535
- ]
536
- },
537
- item.id
538
- );
539
- });
540
- }, [itemsSlot, items, itemClassName, imageClassName, optixFlowConfig]);
694
+ const mediaItems = useMemo(() => {
695
+ if (itemsSlot || !items) return [];
696
+ return items.filter((item) => Boolean(item.image)).map(toMediaItem);
697
+ }, [items, itemsSlot]);
541
698
  if (!itemsSlot && (!items || items.length === 0)) {
542
699
  return null;
543
700
  }
@@ -575,16 +732,34 @@ function InstagramPostGrid({
575
732
  }
576
733
  ) : /* @__PURE__ */ jsx("div", { className: subheadingClassName, children: subheading }))
577
734
  ] }),
578
- /* @__PURE__ */ jsx(
735
+ itemsSlot ? /* @__PURE__ */ jsx(
579
736
  "div",
580
737
  {
581
738
  className: cn(
582
739
  "grid grid-cols-2 gap-2 sm:grid-cols-3 lg:grid-cols-4",
583
740
  gridClassName
584
741
  ),
585
- children: tiles
742
+ children: itemsSlot
586
743
  }
587
- )
744
+ ) : /* @__PURE__ */ jsxs(ImmersiveFeedProvider, { items: mediaItems, actions: VIEWER_ACTIONS, children: [
745
+ /* @__PURE__ */ jsx(
746
+ InstagramFeedGrid,
747
+ {
748
+ mediaItems,
749
+ gridClassName,
750
+ itemClassName,
751
+ imageClassName,
752
+ optixFlowConfig
753
+ }
754
+ ),
755
+ /* @__PURE__ */ jsx(
756
+ ImmersiveViewer,
757
+ {
758
+ ariaLabel: "Instagram post viewer",
759
+ renderActions: ({ item }) => /* @__PURE__ */ jsx(InstagramViewerRail, { item })
760
+ }
761
+ )
762
+ ] })
588
763
  ]
589
764
  }
590
765
  );