@opensite/ui 3.8.2 → 3.10.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.
package/dist/registry.js CHANGED
@@ -35231,6 +35231,193 @@ function InteriorCarousel({
35231
35231
  }
35232
35232
  );
35233
35233
  }
35234
+ function EngagementBadge({
35235
+ iconName,
35236
+ count,
35237
+ label
35238
+ }) {
35239
+ return /* @__PURE__ */ jsxs(
35240
+ "span",
35241
+ {
35242
+ className: "inline-flex items-center gap-1",
35243
+ "aria-label": `${count.toLocaleString()} ${label}`,
35244
+ children: [
35245
+ /* @__PURE__ */ jsx(DynamicIcon, { name: iconName, size: 16, "aria-hidden": "true" }),
35246
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: count.toLocaleString() })
35247
+ ]
35248
+ }
35249
+ );
35250
+ }
35251
+ function InstagramPostGrid({
35252
+ sectionId = "instagram-post-grid",
35253
+ heading,
35254
+ subheading,
35255
+ items,
35256
+ itemsSlot,
35257
+ className,
35258
+ containerClassName,
35259
+ headerClassName,
35260
+ headingClassName,
35261
+ subheadingClassName,
35262
+ gridClassName,
35263
+ itemClassName,
35264
+ imageClassName,
35265
+ background,
35266
+ spacing,
35267
+ pattern,
35268
+ patternOpacity,
35269
+ patternClassName,
35270
+ optixFlowConfig
35271
+ }) {
35272
+ const tiles = useMemo(() => {
35273
+ if (itemsSlot) return itemsSlot;
35274
+ if (!items || items.length === 0) return null;
35275
+ const visibleItems = items.filter((item) => Boolean(item.image));
35276
+ return visibleItems.map((item) => {
35277
+ const altText = item.imageAlt || (typeof item.caption === "string" ? item.caption : "") || "Instagram post";
35278
+ const showVideo = Boolean(item.isVideo && item.videoUrl);
35279
+ return /* @__PURE__ */ jsxs(
35280
+ Pressable,
35281
+ {
35282
+ href: item.href,
35283
+ "aria-label": typeof item.caption === "string" && item.caption.length > 0 ? item.caption : "View Instagram post",
35284
+ className: cn(
35285
+ "group relative block aspect-square overflow-hidden rounded-md bg-muted",
35286
+ item.className,
35287
+ itemClassName
35288
+ ),
35289
+ children: [
35290
+ /* @__PURE__ */ jsx(
35291
+ Img,
35292
+ {
35293
+ src: item.image,
35294
+ alt: altText,
35295
+ loading: "lazy",
35296
+ className: cn(
35297
+ "h-full w-full object-cover object-center transition-transform duration-300 group-hover:scale-105 motion-reduce:transform-none motion-reduce:transition-none",
35298
+ imageClassName
35299
+ ),
35300
+ optixFlowConfig
35301
+ }
35302
+ ),
35303
+ showVideo && /* @__PURE__ */ jsx(
35304
+ Video,
35305
+ {
35306
+ src: item.videoUrl,
35307
+ poster: item.image,
35308
+ controls: false,
35309
+ muted: true,
35310
+ loop: true,
35311
+ playsInline: true,
35312
+ preload: "metadata",
35313
+ onMouseEnter: (e) => {
35314
+ void e.currentTarget.play().catch(() => void 0);
35315
+ },
35316
+ onMouseLeave: (e) => {
35317
+ e.currentTarget.pause();
35318
+ e.currentTarget.currentTime = 0;
35319
+ },
35320
+ className: cn(
35321
+ "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",
35322
+ imageClassName
35323
+ )
35324
+ }
35325
+ ),
35326
+ item.isVideo && /* @__PURE__ */ jsx(
35327
+ "span",
35328
+ {
35329
+ className: "absolute right-2 top-2 text-white drop-shadow-md",
35330
+ "aria-hidden": "true",
35331
+ children: /* @__PURE__ */ jsx(DynamicIcon, { name: "lucide/play", size: 18 })
35332
+ }
35333
+ ),
35334
+ (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: [
35335
+ 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 })),
35336
+ (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: [
35337
+ typeof item.likeCount === "number" && /* @__PURE__ */ jsx(
35338
+ EngagementBadge,
35339
+ {
35340
+ iconName: "lucide/heart",
35341
+ count: item.likeCount,
35342
+ label: "likes"
35343
+ }
35344
+ ),
35345
+ typeof item.commentCount === "number" && /* @__PURE__ */ jsx(
35346
+ EngagementBadge,
35347
+ {
35348
+ iconName: "lucide/message-circle",
35349
+ count: item.commentCount,
35350
+ label: "comments"
35351
+ }
35352
+ ),
35353
+ typeof item.viewCount === "number" && /* @__PURE__ */ jsx(
35354
+ EngagementBadge,
35355
+ {
35356
+ iconName: "lucide/eye",
35357
+ count: item.viewCount,
35358
+ label: "views"
35359
+ }
35360
+ ),
35361
+ item.date && /* @__PURE__ */ jsx("span", { className: "ml-auto opacity-80", children: item.date })
35362
+ ] })
35363
+ ] })
35364
+ ]
35365
+ },
35366
+ item.id
35367
+ );
35368
+ });
35369
+ }, [itemsSlot, items, itemClassName, imageClassName, optixFlowConfig]);
35370
+ if (!itemsSlot && (!items || items.length === 0)) {
35371
+ return null;
35372
+ }
35373
+ return /* @__PURE__ */ jsxs(
35374
+ Section,
35375
+ {
35376
+ id: sectionId,
35377
+ background,
35378
+ spacing,
35379
+ pattern,
35380
+ patternOpacity,
35381
+ patternClassName,
35382
+ className,
35383
+ containerClassName,
35384
+ children: [
35385
+ (heading || subheading) && /* @__PURE__ */ jsxs("div", { className: cn("mb-8 flex flex-col gap-2", headerClassName), children: [
35386
+ heading && (typeof heading === "string" ? /* @__PURE__ */ jsx(
35387
+ "h2",
35388
+ {
35389
+ className: cn(
35390
+ "text-xl font-medium tracking-tight md:text-2xl lg:text-3xl text-balance",
35391
+ headingClassName
35392
+ ),
35393
+ children: heading
35394
+ }
35395
+ ) : /* @__PURE__ */ jsx("div", { className: headingClassName, children: heading })),
35396
+ subheading && (typeof subheading === "string" ? /* @__PURE__ */ jsx(
35397
+ "p",
35398
+ {
35399
+ className: cn(
35400
+ "max-w-2xl text-balance text-muted-foreground",
35401
+ subheadingClassName
35402
+ ),
35403
+ children: subheading
35404
+ }
35405
+ ) : /* @__PURE__ */ jsx("div", { className: subheadingClassName, children: subheading }))
35406
+ ] }),
35407
+ /* @__PURE__ */ jsx(
35408
+ "div",
35409
+ {
35410
+ className: cn(
35411
+ "grid grid-cols-2 gap-2 sm:grid-cols-3 lg:grid-cols-4",
35412
+ gridClassName
35413
+ ),
35414
+ children: tiles
35415
+ }
35416
+ )
35417
+ ]
35418
+ }
35419
+ );
35420
+ }
35234
35421
  function RadialGradientTop({
35235
35422
  className,
35236
35423
  children,
@@ -111106,6 +111293,75 @@ var interiorCarousel = {
111106
111293
  ]
111107
111294
  }
111108
111295
  };
111296
+ var instagramPostGrid = {
111297
+ exampleUsage: "Display a website's Instagram feed as a responsive grid of square tiles. Each tile links out to the post's instagram.com permalink in a new tab, shows the caption and post date on hover, and renders like/comment/view counts when available. Video posts play muted on hover. Data is hydrated from the toastability Instagram feed (dataSource type 'instagram_feed', bindTo 'items').",
111298
+ importantUsageNotes: `${GALLERY_MEDIA_NOTE} This is a DYNAMIC feed block: its 'items' are hydrated at routing-build time from the connected Instagram profile \u2014 do NOT hand-author post content. 'items[].image' and 'items[].videoUrl' MUST be the re-hosted MediaRecord CDN URLs served by the feed; expiring Instagram CDN URLs must never be used. Engagement counts ('likeCount', 'commentCount', 'viewCount') render only when the value is present \u2014 never fabricate a zero for a missing metric. Each 'items[].href' is an external instagram.com permalink and opens in a new tab. Items without an 'image' are skipped; an empty or missing 'items' array renders nothing. Requires the site to have a connected, fully-imported Instagram profile ('instagram_media' capability).`,
111299
+ usageRequirements: {
111300
+ requiredProps: ["items"],
111301
+ requiresSiteCapabilities: galleryCapabilities(
111302
+ "instagram_media",
111303
+ "media_library"
111304
+ ),
111305
+ propConstraints: {
111306
+ items: {
111307
+ minItems: 3,
111308
+ maxItems: 24
111309
+ }
111310
+ },
111311
+ mediaSlots: {
111312
+ "items[].image": {
111313
+ path: "items[].image",
111314
+ roles: ["gallery"],
111315
+ required: true
111316
+ },
111317
+ "items[].videoUrl": {
111318
+ path: "items[].videoUrl",
111319
+ roles: ["gallery"],
111320
+ required: false
111321
+ }
111322
+ }
111323
+ },
111324
+ exampleProps: {
111325
+ heading: "Follow us on Instagram",
111326
+ subheading: "The latest from our feed.",
111327
+ background: "white",
111328
+ spacing: "lg",
111329
+ items: [
111330
+ {
111331
+ id: "1",
111332
+ href: "https://www.instagram.com/p/CxAmpLe001/",
111333
+ image: GALLERY_EXAMPLE_IMAGE_URL,
111334
+ imageAlt: "Espresso being poured at the counter",
111335
+ caption: "Morning pours before the rush \u2615\uFE0F",
111336
+ date: "Jul 1, 2026",
111337
+ likeCount: 128,
111338
+ commentCount: 12
111339
+ },
111340
+ {
111341
+ id: "2",
111342
+ href: "https://www.instagram.com/p/CxAmpLe002/",
111343
+ image: GALLERY_EXAMPLE_IMAGE_URL,
111344
+ imageAlt: "Behind the scenes reel",
111345
+ caption: "Behind the scenes of today's bake #freshbread",
111346
+ date: "Jun 28, 2026",
111347
+ isVideo: true,
111348
+ videoUrl: "https://toastability-production.s3.amazonaws.com/4kox2ux0ye1wlqkdwg03s08a67i1",
111349
+ likeCount: 342,
111350
+ commentCount: 21,
111351
+ viewCount: 4820
111352
+ },
111353
+ {
111354
+ id: "3",
111355
+ href: "https://www.instagram.com/p/CxAmpLe003/",
111356
+ image: GALLERY_EXAMPLE_IMAGE_URL,
111357
+ imageAlt: "Weekend brunch spread",
111358
+ caption: "Weekend brunch is back on the menu",
111359
+ date: "Jun 24, 2026",
111360
+ likeCount: 96
111361
+ }
111362
+ ]
111363
+ }
111364
+ };
111109
111365
  var GALLERY_BLOCK_CONTRACTS = {
111110
111366
  "expandable-case-study-cards": expandableCaseStudyCards,
111111
111367
  "carousel-badge-cards": carouselBadgeCards,
@@ -111122,7 +111378,8 @@ var GALLERY_BLOCK_CONTRACTS = {
111122
111378
  "carousel-scale-focus": carouselScaleFocus,
111123
111379
  "masonry-motion-grid": masonryMotionGrid,
111124
111380
  "blur-vignette-grid": blurVignetteGrid,
111125
- "interior-carousel": interiorCarousel
111381
+ "interior-carousel": interiorCarousel,
111382
+ "instagram-post-grid": instagramPostGrid
111126
111383
  };
111127
111384
  var FOOTER_EXAMPLE_IMAGE_URL = "https://cdn.ing/assets/i/r/308196/g6bbn73f7gxal82uu49m9prfd0u8/workplace-in-cafe.webp";
111128
111385
  var FOOTER_MEDIA_NOTE = "All media src values must be absolute URLs to real assets; relative paths and placeholder media variables are not allowed.";
@@ -124517,6 +124774,26 @@ var BLOCK_REGISTRY = {
124517
124774
  props: "InteriorCarouselProps",
124518
124775
  ...GALLERY_BLOCK_CONTRACTS["interior-carousel"]
124519
124776
  },
124777
+ "instagram-post-grid": {
124778
+ id: "instagram-post-grid",
124779
+ name: "Instagram Post Grid",
124780
+ description: "A responsive grid of square tiles rendering a website's Instagram feed. Each tile links out to the post's instagram.com permalink in a new tab, reveals the caption, date, and like/comment/view counts on hover, and plays video posts muted on hover. Content is hydrated dynamically from the connected Instagram profile. Ideal for social proof sections, footers, and community pages that showcase a live Instagram presence.",
124781
+ semanticTags: [
124782
+ "gallery",
124783
+ "instagram",
124784
+ "social",
124785
+ "feed",
124786
+ "grid",
124787
+ "photos",
124788
+ "video",
124789
+ "dynamic",
124790
+ "community"
124791
+ ],
124792
+ category: "gallery",
124793
+ component: InstagramPostGrid,
124794
+ props: "InstagramPostGridProps",
124795
+ ...GALLERY_BLOCK_CONTRACTS["instagram-post-grid"]
124796
+ },
124520
124797
  "radial-gradient-top": {
124521
124798
  id: "radial-gradient-top",
124522
124799
  name: "Radial Gradient Top",
@@ -135174,7 +135451,51 @@ function createBuilderContractBundle({
135174
135451
  hydrationPhase: "routing-build",
135175
135452
  canonicalPayloadExpectation: "Keep blog feed requests symbolic in canonical page JSON until routing-build hydration resolves them.",
135176
135453
  requiredFields: ["type"],
135177
- optionalFields: ["limit", "offset", "category", "tag", "featuredOnly"]
135454
+ optionalFields: [
135455
+ "limit",
135456
+ "offset",
135457
+ "category",
135458
+ "tag",
135459
+ "featuredOnly",
135460
+ "bindTo"
135461
+ ]
135462
+ },
135463
+ blog_post: {
135464
+ sourceType: "blog_post",
135465
+ symbolic: true,
135466
+ hydrationOwner: "dashtrack-ai",
135467
+ hydrationPhase: "routing-build",
135468
+ canonicalPayloadExpectation: "Keep blog post requests symbolic in canonical page JSON until routing-build hydration resolves them.",
135469
+ requiredFields: ["type"],
135470
+ optionalFields: ["slug", "current", "bindTo"]
135471
+ },
135472
+ testimonials_feed: {
135473
+ sourceType: "testimonials_feed",
135474
+ symbolic: true,
135475
+ hydrationOwner: "dashtrack-ai",
135476
+ hydrationPhase: "routing-build",
135477
+ canonicalPayloadExpectation: "Keep testimonials feed requests symbolic in canonical page JSON until routing-build hydration resolves them.",
135478
+ requiredFields: ["type"],
135479
+ optionalFields: ["limit", "minRating", "platforms", "locationId", "bindTo"]
135480
+ },
135481
+ instagram_feed: {
135482
+ sourceType: "instagram_feed",
135483
+ symbolic: true,
135484
+ hydrationOwner: "dashtrack-ai",
135485
+ hydrationPhase: "routing-build",
135486
+ canonicalPayloadExpectation: "Keep Instagram feed requests symbolic in canonical page JSON until routing-build hydration resolves them.",
135487
+ requiredFields: ["type"],
135488
+ optionalFields: ["limit", "profile", "hashtag", "bindTo"]
135489
+ },
135490
+ events_feed: {
135491
+ sourceType: "events_feed",
135492
+ symbolic: true,
135493
+ hydrationOwner: "dashtrack-ai",
135494
+ hydrationPhase: "routing-build",
135495
+ canonicalPayloadExpectation: "Keep events feed requests symbolic in canonical page JSON until routing-build hydration resolves them.",
135496
+ requiredFields: ["type"],
135497
+ optionalFields: ["limit", "upcomingOnly", "locationIds", "bindTo"],
135498
+ expands: true
135178
135499
  }
135179
135500
  },
135180
135501
  designTokens: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opensite/ui",
3
- "version": "3.8.2",
3
+ "version": "3.10.0",
4
4
  "description": "Foundational UI component library for OpenSite Semantic Site Builder with tree-shakable exports and abstract styling",
5
5
  "keywords": [
6
6
  "react",
@@ -1376,6 +1376,11 @@
1376
1376
  "import": "./dist/expandable-case-study-cards.js",
1377
1377
  "require": "./dist/expandable-case-study-cards.cjs"
1378
1378
  },
1379
+ "./blocks/gallery/instagram-post-grid": {
1380
+ "types": "./dist/instagram-post-grid.d.ts",
1381
+ "import": "./dist/instagram-post-grid.js",
1382
+ "require": "./dist/instagram-post-grid.cjs"
1383
+ },
1379
1384
  "./blocks/gallery/interior-carousel": {
1380
1385
  "types": "./dist/interior-carousel.d.ts",
1381
1386
  "import": "./dist/interior-carousel.js",