@harshit-wander/component-lib 1.1.10 → 1.1.12

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/index.d.cts CHANGED
@@ -80,6 +80,18 @@ interface EventVideoBannerProps extends HTMLAttributes<HTMLElement> {
80
80
  videoUrl: string;
81
81
  alt: string;
82
82
  href?: string | undefined;
83
+ /**
84
+ * Optional mobile-specific video source. When the viewport is <= 767px,
85
+ * EventVideoBanner uses this instead of `videoUrl`. The selection happens
86
+ * in a client-only effect, so no desktop bytes are requested on mobile.
87
+ */
88
+ mobileVideoUrl?: string | undefined;
89
+ /**
90
+ * Optional mobile-specific poster image. Used in the inactive (poster-only)
91
+ * state via a `<picture>` element with a media-query <source>, so the
92
+ * browser fetches the smallest variant for the active viewport.
93
+ */
94
+ mobilePosterUrl?: string | undefined;
83
95
  /**
84
96
  * When false, renders only the poster image (no <video> element, no HLS load).
85
97
  * Used by EventCarousel to avoid loading every video in the carousel at once.
@@ -219,6 +231,12 @@ declare const TestimonialCard: react.ForwardRefExoticComponent<TestimonialCardPr
219
231
 
220
232
  interface TripCategoryCardProps extends HTMLAttributes<HTMLElement> {
221
233
  image: string;
234
+ /**
235
+ * Optional mobile-specific image. When provided, the card emits a
236
+ * `<picture>` with a `(max-width: 767px)` source so narrow viewports
237
+ * fetch the smaller variant. No JS, no hydration mismatch.
238
+ */
239
+ mobileImage?: string | undefined;
222
240
  alt: string;
223
241
  destination: string;
224
242
  startingPrice: string;
@@ -361,6 +379,9 @@ interface EventCarouselVideoItem {
361
379
  videoUrl: string;
362
380
  alt: string;
363
381
  href?: string | undefined;
382
+ /** Optional viewport-specific overrides used at <= 767px. */
383
+ mobileVideoUrl?: string | undefined;
384
+ mobilePosterUrl?: string | undefined;
364
385
  }
365
386
  type BaseProps = HTMLAttributes<HTMLElement>;
366
387
  type EventCarouselProps = (BaseProps & {
@@ -492,6 +513,13 @@ interface HomeHeroProps extends HTMLAttributes<HTMLElement> {
492
513
  videoUrl: string;
493
514
  /** Poster image URL shown before the video loads and on browsers that block autoplay. */
494
515
  posterUrl: string;
516
+ /**
517
+ * Optional mobile-specific poster. When provided, the component swaps the
518
+ * `<video poster>` attribute to this URL on viewports <= 767px in a
519
+ * client-only effect. Pair with `<link rel="preload" imageSrcSet>` in the
520
+ * page head to actually save bytes on mobile.
521
+ */
522
+ mobilePosterUrl?: string | undefined;
495
523
  /** Static heading. White, bold, large. */
496
524
  title: string;
497
525
  /**
@@ -628,6 +656,8 @@ interface TripsCategorySectionCta {
628
656
  }
629
657
  interface TripsCategoryItem {
630
658
  image: string;
659
+ /** Optional mobile-specific image for the destination card. */
660
+ mobileImage?: string | undefined;
631
661
  alt: string;
632
662
  destination: string;
633
663
  startingPrice: string;
package/dist/index.d.ts CHANGED
@@ -80,6 +80,18 @@ interface EventVideoBannerProps extends HTMLAttributes<HTMLElement> {
80
80
  videoUrl: string;
81
81
  alt: string;
82
82
  href?: string | undefined;
83
+ /**
84
+ * Optional mobile-specific video source. When the viewport is <= 767px,
85
+ * EventVideoBanner uses this instead of `videoUrl`. The selection happens
86
+ * in a client-only effect, so no desktop bytes are requested on mobile.
87
+ */
88
+ mobileVideoUrl?: string | undefined;
89
+ /**
90
+ * Optional mobile-specific poster image. Used in the inactive (poster-only)
91
+ * state via a `<picture>` element with a media-query <source>, so the
92
+ * browser fetches the smallest variant for the active viewport.
93
+ */
94
+ mobilePosterUrl?: string | undefined;
83
95
  /**
84
96
  * When false, renders only the poster image (no <video> element, no HLS load).
85
97
  * Used by EventCarousel to avoid loading every video in the carousel at once.
@@ -219,6 +231,12 @@ declare const TestimonialCard: react.ForwardRefExoticComponent<TestimonialCardPr
219
231
 
220
232
  interface TripCategoryCardProps extends HTMLAttributes<HTMLElement> {
221
233
  image: string;
234
+ /**
235
+ * Optional mobile-specific image. When provided, the card emits a
236
+ * `<picture>` with a `(max-width: 767px)` source so narrow viewports
237
+ * fetch the smaller variant. No JS, no hydration mismatch.
238
+ */
239
+ mobileImage?: string | undefined;
222
240
  alt: string;
223
241
  destination: string;
224
242
  startingPrice: string;
@@ -361,6 +379,9 @@ interface EventCarouselVideoItem {
361
379
  videoUrl: string;
362
380
  alt: string;
363
381
  href?: string | undefined;
382
+ /** Optional viewport-specific overrides used at <= 767px. */
383
+ mobileVideoUrl?: string | undefined;
384
+ mobilePosterUrl?: string | undefined;
364
385
  }
365
386
  type BaseProps = HTMLAttributes<HTMLElement>;
366
387
  type EventCarouselProps = (BaseProps & {
@@ -492,6 +513,13 @@ interface HomeHeroProps extends HTMLAttributes<HTMLElement> {
492
513
  videoUrl: string;
493
514
  /** Poster image URL shown before the video loads and on browsers that block autoplay. */
494
515
  posterUrl: string;
516
+ /**
517
+ * Optional mobile-specific poster. When provided, the component swaps the
518
+ * `<video poster>` attribute to this URL on viewports <= 767px in a
519
+ * client-only effect. Pair with `<link rel="preload" imageSrcSet>` in the
520
+ * page head to actually save bytes on mobile.
521
+ */
522
+ mobilePosterUrl?: string | undefined;
495
523
  /** Static heading. White, bold, large. */
496
524
  title: string;
497
525
  /**
@@ -628,6 +656,8 @@ interface TripsCategorySectionCta {
628
656
  }
629
657
  interface TripsCategoryItem {
630
658
  image: string;
659
+ /** Optional mobile-specific image for the destination card. */
660
+ mobileImage?: string | undefined;
631
661
  alt: string;
632
662
  destination: string;
633
663
  startingPrice: string;
package/dist/index.js CHANGED
@@ -272,31 +272,65 @@ var DestinationCard = forwardRef(
272
272
  DestinationCard.displayName = "DestinationCard";
273
273
  var frameClass = "block w-full h-[300px] overflow-hidden rounded-md no-underline text-inherit focus-visible:outline-2 focus-visible:outline-accent focus-visible:outline-offset-2";
274
274
  var EventBanner = forwardRef(
275
- ({ imageUrl, alt, href, className, ...rest }, ref) => /* @__PURE__ */ jsx("div", { ref, className: cn("shrink-0 w-full snap-start", className), ...rest, children: href ? /* @__PURE__ */ jsx("a", { href, className: frameClass, children: /* @__PURE__ */ jsx("img", { src: imageUrl, alt, className: "block w-full h-full object-cover" }) }) : /* @__PURE__ */ jsx("div", { className: frameClass, children: /* @__PURE__ */ jsx("img", { src: imageUrl, alt, className: "block w-full h-full object-cover" }) }) })
275
+ ({ imageUrl, alt, href, className, ...rest }, ref) => /* @__PURE__ */ jsx("div", { ref, className: cn("shrink-0 w-full snap-start", className), ...rest, children: href ? /* @__PURE__ */ jsx("a", { href, className: frameClass, children: /* @__PURE__ */ jsx(
276
+ "img",
277
+ {
278
+ src: imageUrl,
279
+ alt,
280
+ loading: "lazy",
281
+ decoding: "async",
282
+ className: "block w-full h-full object-cover"
283
+ }
284
+ ) }) : /* @__PURE__ */ jsx("div", { className: frameClass, children: /* @__PURE__ */ jsx(
285
+ "img",
286
+ {
287
+ src: imageUrl,
288
+ alt,
289
+ loading: "lazy",
290
+ decoding: "async",
291
+ className: "block w-full h-full object-cover"
292
+ }
293
+ ) }) })
276
294
  );
277
295
  EventBanner.displayName = "EventBanner";
278
296
  var frameClass2 = "block w-full h-[300px] overflow-hidden rounded-md no-underline text-inherit focus-visible:outline-2 focus-visible:outline-accent focus-visible:outline-offset-2";
279
297
  var mediaClass = "block w-full h-full object-cover";
280
298
  var EventVideoBanner = forwardRef(
281
- ({ posterUrl, videoUrl, alt, href, isActive = true, className, ...rest }, ref) => {
299
+ ({
300
+ posterUrl,
301
+ videoUrl,
302
+ alt,
303
+ href,
304
+ mobileVideoUrl,
305
+ mobilePosterUrl,
306
+ isActive = true,
307
+ className,
308
+ ...rest
309
+ }, ref) => {
282
310
  const videoRef = useRef(null);
283
311
  useEffect(() => {
284
312
  if (!isActive) return;
285
313
  const video = videoRef.current;
286
314
  if (!video || !videoUrl) return;
287
- if (!videoUrl.includes(".m3u8")) {
288
- video.src = videoUrl;
315
+ const isMobileViewport = typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia("(max-width: 767px)").matches;
316
+ const activeUrl = isMobileViewport && mobileVideoUrl ? mobileVideoUrl : videoUrl;
317
+ const activePoster = isMobileViewport && mobilePosterUrl ? mobilePosterUrl : posterUrl;
318
+ if (activePoster && video.poster !== activePoster) {
319
+ video.poster = activePoster;
320
+ }
321
+ if (!activeUrl.includes(".m3u8")) {
322
+ video.src = activeUrl;
289
323
  return;
290
324
  }
291
325
  if (video.canPlayType("application/vnd.apple.mpegurl")) {
292
- video.src = videoUrl;
326
+ video.src = activeUrl;
293
327
  return;
294
328
  }
295
329
  let cancelled = false;
296
330
  import('hls.js').then(({ default: Hls }) => {
297
331
  if (cancelled || !Hls.isSupported()) return;
298
332
  const hls = new Hls({ autoStartLoad: true, startLevel: -1 });
299
- hls.loadSource(videoUrl);
333
+ hls.loadSource(activeUrl);
300
334
  hls.attachMedia(video);
301
335
  video._hls = hls;
302
336
  });
@@ -305,12 +339,11 @@ var EventVideoBanner = forwardRef(
305
339
  const stored = video._hls;
306
340
  stored?.destroy();
307
341
  };
308
- }, [videoUrl, isActive]);
342
+ }, [videoUrl, mobileVideoUrl, posterUrl, mobilePosterUrl, isActive]);
309
343
  const mediaEl = isActive ? /* @__PURE__ */ jsx(
310
344
  "video",
311
345
  {
312
346
  ref: videoRef,
313
- src: videoUrl?.includes(".m3u8") ? void 0 : videoUrl,
314
347
  poster: posterUrl,
315
348
  "aria-label": alt,
316
349
  muted: true,
@@ -320,7 +353,13 @@ var EventVideoBanner = forwardRef(
320
353
  preload: "metadata",
321
354
  className: mediaClass
322
355
  }
323
- ) : /* @__PURE__ */ jsx("img", { src: posterUrl, alt, loading: "lazy", decoding: "async", className: mediaClass });
356
+ ) : (
357
+ // <picture> lets the browser pick mobile/desktop poster via CSS, no JS
358
+ /* @__PURE__ */ jsxs("picture", { children: [
359
+ mobilePosterUrl ? /* @__PURE__ */ jsx("source", { media: "(max-width: 767px)", srcSet: mobilePosterUrl }) : null,
360
+ /* @__PURE__ */ jsx("img", { src: posterUrl, alt, loading: "lazy", decoding: "async", className: mediaClass })
361
+ ] })
362
+ );
324
363
  return /* @__PURE__ */ jsx("div", { ref, className: cn("shrink-0 w-full snap-start", className), ...rest, children: href ? /* @__PURE__ */ jsx("a", { href, className: frameClass2, children: mediaEl }) : /* @__PURE__ */ jsx("div", { className: frameClass2, children: mediaEl }) });
325
364
  }
326
365
  );
@@ -394,7 +433,16 @@ var cardClass2 = "shrink-0 w-[132px] flex flex-col bg-surface border border-bord
394
433
  var ExploreCard = forwardRef(
395
434
  ({ image, alt, label, href, className, ...rest }, ref) => {
396
435
  const content = /* @__PURE__ */ jsxs(Fragment, { children: [
397
- /* @__PURE__ */ jsx("img", { src: image, alt, className: "block w-full h-[102px] object-cover" }),
436
+ /* @__PURE__ */ jsx(
437
+ "img",
438
+ {
439
+ src: image,
440
+ alt,
441
+ loading: "lazy",
442
+ decoding: "async",
443
+ className: "block w-full h-[102px] object-cover"
444
+ }
445
+ ),
398
446
  /* @__PURE__ */ jsx("span", { className: "flex items-center justify-center min-h-[56px] py-sm px-md text-md font-medium leading-card text-secondary text-center", children: label })
399
447
  ] });
400
448
  if (href) {
@@ -517,7 +565,16 @@ var GalleryPhoto = forwardRef(
517
565
  ),
518
566
  ...rest,
519
567
  children: [
520
- /* @__PURE__ */ jsx("img", { src: image, alt, className: "block w-full h-full object-cover" }),
568
+ /* @__PURE__ */ jsx(
569
+ "img",
570
+ {
571
+ src: image,
572
+ alt,
573
+ loading: "lazy",
574
+ decoding: "async",
575
+ className: "block w-full h-full object-cover"
576
+ }
577
+ ),
521
578
  /* @__PURE__ */ jsxs("div", { className: "absolute bottom-12 left-sm inline-flex items-center gap-xs py-[2px] px-[6px] bg-black/40 border-[0.5px] border-secondary rounded-full text-on-dark text-xs font-normal leading-4 max-md:bottom-8", children: [
522
579
  /* @__PURE__ */ jsx(
523
580
  "span",
@@ -773,7 +830,7 @@ var TestimonialCard = forwardRef(
773
830
  ({ thumbnail, thumbnailAlt, logo, logoAlt, caption, videoHref, onPlayClick, className, ...rest }, ref) => {
774
831
  const playLabel = `Play testimonial: ${caption}`;
775
832
  const inner = /* @__PURE__ */ jsxs(Fragment, { children: [
776
- /* @__PURE__ */ jsx("img", { src: thumbnail, alt: thumbnailAlt }),
833
+ /* @__PURE__ */ jsx("img", { src: thumbnail, alt: thumbnailAlt, loading: "lazy", decoding: "async" }),
777
834
  /* @__PURE__ */ jsx("span", { className: "absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 inline-flex items-center justify-center w-[56px] h-[56px] pointer-events-none z-[1] text-white", children: /* @__PURE__ */ jsx(PlaySvg, {}) })
778
835
  ] });
779
836
  return /* @__PURE__ */ jsxs(
@@ -808,7 +865,16 @@ var TestimonialCard = forwardRef(
808
865
  }
809
866
  ),
810
867
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-sm w-full py-md", children: [
811
- /* @__PURE__ */ jsx("img", { src: logo, alt: logoAlt, className: "w-[60px] h-[60px] object-contain shrink-0" }),
868
+ /* @__PURE__ */ jsx(
869
+ "img",
870
+ {
871
+ src: logo,
872
+ alt: logoAlt,
873
+ loading: "lazy",
874
+ decoding: "async",
875
+ className: "w-[60px] h-[60px] object-contain shrink-0"
876
+ }
877
+ ),
812
878
  /* @__PURE__ */ jsx("p", { className: "m-0 text-md font-medium leading-8 text-secondary", children: caption })
813
879
  ] })
814
880
  ]
@@ -819,17 +885,21 @@ var TestimonialCard = forwardRef(
819
885
  TestimonialCard.displayName = "TestimonialCard";
820
886
  var cardClass4 = "relative flex flex-col justify-end shrink-0 w-[200px] h-[316px] p-[20px] border-2 border-surface rounded-lg overflow-hidden no-underline text-inherit snap-start focus-visible:outline-2 focus-visible:outline-accent focus-visible:outline-offset-2";
821
887
  var TripCategoryCard = forwardRef(
822
- ({ image, alt, destination, startingPrice, href, className, ...rest }, ref) => {
888
+ ({ image, mobileImage, alt, destination, startingPrice, href, className, ...rest }, ref) => {
823
889
  const content = /* @__PURE__ */ jsxs(Fragment, { children: [
824
- /* @__PURE__ */ jsx(
825
- "span",
826
- {
827
- role: "img",
828
- "aria-label": alt,
829
- className: "absolute inset-0 bg-cover bg-center pointer-events-none",
830
- style: { backgroundImage: `url(${image})` }
831
- }
832
- ),
890
+ /* @__PURE__ */ jsxs("picture", { className: "absolute inset-0 pointer-events-none", children: [
891
+ mobileImage ? /* @__PURE__ */ jsx("source", { media: "(max-width: 767px)", srcSet: mobileImage }) : null,
892
+ /* @__PURE__ */ jsx(
893
+ "img",
894
+ {
895
+ src: image,
896
+ alt,
897
+ loading: "lazy",
898
+ decoding: "async",
899
+ className: "absolute inset-0 w-full h-full object-cover"
900
+ }
901
+ )
902
+ ] }),
833
903
  /* @__PURE__ */ jsx(
834
904
  "span",
835
905
  {
@@ -1682,6 +1752,8 @@ var EventCarousel = forwardRef(
1682
1752
  {
1683
1753
  posterUrl: event.posterUrl,
1684
1754
  videoUrl: event.videoUrl,
1755
+ mobileVideoUrl: event.mobileVideoUrl,
1756
+ mobilePosterUrl: event.mobilePosterUrl,
1685
1757
  alt: event.alt,
1686
1758
  href: event.href,
1687
1759
  isActive: index === activeIndex
@@ -2226,9 +2298,28 @@ var useTypewriter = (phrases, options) => {
2226
2298
  return text;
2227
2299
  };
2228
2300
  var HomeHero = forwardRef(
2229
- ({ videoUrl, posterUrl, title, cyclingPhrases, typewriter, reviews, className, ...rest }, ref) => {
2301
+ ({
2302
+ videoUrl,
2303
+ posterUrl,
2304
+ mobilePosterUrl,
2305
+ title,
2306
+ cyclingPhrases,
2307
+ typewriter,
2308
+ reviews,
2309
+ className,
2310
+ ...rest
2311
+ }, ref) => {
2230
2312
  const currentPhrase = useTypewriter(cyclingPhrases, typewriter);
2231
2313
  const hasReviews = reviews !== void 0 && reviews.length > 0;
2314
+ const videoRef = useRef(null);
2315
+ useEffect(() => {
2316
+ const video = videoRef.current;
2317
+ if (!video || !mobilePosterUrl) return;
2318
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
2319
+ if (window.matchMedia("(max-width: 767px)").matches) {
2320
+ video.poster = mobilePosterUrl;
2321
+ }
2322
+ }, [mobilePosterUrl]);
2232
2323
  return /* @__PURE__ */ jsxs(
2233
2324
  "section",
2234
2325
  {
@@ -2239,6 +2330,7 @@ var HomeHero = forwardRef(
2239
2330
  /* @__PURE__ */ jsx(
2240
2331
  "video",
2241
2332
  {
2333
+ ref: videoRef,
2242
2334
  src: videoUrl,
2243
2335
  poster: posterUrl,
2244
2336
  muted: true,
@@ -2862,6 +2954,7 @@ var TripsCategorySection = forwardRef(
2862
2954
  TripCategoryCard,
2863
2955
  {
2864
2956
  image: trip.image,
2957
+ mobileImage: trip.mobileImage,
2865
2958
  alt: trip.alt,
2866
2959
  destination: trip.destination,
2867
2960
  startingPrice: trip.startingPrice,
@@ -2917,6 +3010,7 @@ var TripsCategorySection = forwardRef(
2917
3010
  TripCategoryCard,
2918
3011
  {
2919
3012
  image: trip.image,
3013
+ mobileImage: trip.mobileImage,
2920
3014
  alt: trip.alt,
2921
3015
  destination: trip.destination,
2922
3016
  startingPrice: trip.startingPrice,