@planetaexo/design-system 0.93.1 → 0.95.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/index.d.cts CHANGED
@@ -3174,7 +3174,18 @@ declare function MenuTrip({ sections, activeSection, onSelect, variant, bold, cl
3174
3174
 
3175
3175
  type PhotoGalleryVariant = "grid" | "gridCompact" | "masonry" | "filmstrip" | "featured" | "collage" | "collageTight" | "carousel" | "fullBleed";
3176
3176
  interface PhotoGalleryPhoto {
3177
+ /**
3178
+ * Full-resolution image — shown in the lightbox / opened photo tour, and the
3179
+ * fallback the grid thumbnails use when {@link thumbSrc} is omitted.
3180
+ */
3177
3181
  src: string;
3182
+ /**
3183
+ * Optional smaller image for the grid thumbnails (grid/gridCompact/masonry/
3184
+ * filmstrip/featured/collage tiles). Lets a consumer serve a lightweight,
3185
+ * resized variant in the gallery grid while the lightbox still opens the
3186
+ * full-resolution {@link src}. Falls back to {@link src} when omitted.
3187
+ */
3188
+ thumbSrc?: string;
3178
3189
  alt?: string;
3179
3190
  /** Caption displayed in the lightbox bottom-left */
3180
3191
  caption?: string;
@@ -5429,10 +5440,13 @@ interface ReviewsSpotlightProps {
5429
5440
  className?: string;
5430
5441
  }
5431
5442
  /**
5432
- * ReviewsSpotlight — a single rotating testimonial. One large serif quote with
5433
- * Trustpilot stars, auto-advancing through `items` with clickable dots and a
5434
- * short crossfade. Honours `prefers-reduced-motion` (no auto-advance, instant
5435
- * swap). The dark band matches the rest of {@link NewHome}.
5443
+ * ReviewsSpotlight — a swipeable row of testimonials. Each review is a
5444
+ * full-width slide on a native horizontal scroll-snap track, so on touch it
5445
+ * can be flicked one review at a time; on desktop the dots advance it. One
5446
+ * large serif quote with Trustpilot stars, auto-advancing through `items` with
5447
+ * the pagination dots synced to scroll position. Autoplay pauses on
5448
+ * hover / focus / active swipe and is skipped entirely under
5449
+ * `prefers-reduced-motion`. The dark band matches the rest of {@link NewHome}.
5436
5450
  */
5437
5451
  declare function ReviewsSpotlight({ eyebrow, title, link, items, intervalMs, className, }: ReviewsSpotlightProps): react_jsx_runtime.JSX.Element | null;
5438
5452
 
package/dist/index.d.ts CHANGED
@@ -3174,7 +3174,18 @@ declare function MenuTrip({ sections, activeSection, onSelect, variant, bold, cl
3174
3174
 
3175
3175
  type PhotoGalleryVariant = "grid" | "gridCompact" | "masonry" | "filmstrip" | "featured" | "collage" | "collageTight" | "carousel" | "fullBleed";
3176
3176
  interface PhotoGalleryPhoto {
3177
+ /**
3178
+ * Full-resolution image — shown in the lightbox / opened photo tour, and the
3179
+ * fallback the grid thumbnails use when {@link thumbSrc} is omitted.
3180
+ */
3177
3181
  src: string;
3182
+ /**
3183
+ * Optional smaller image for the grid thumbnails (grid/gridCompact/masonry/
3184
+ * filmstrip/featured/collage tiles). Lets a consumer serve a lightweight,
3185
+ * resized variant in the gallery grid while the lightbox still opens the
3186
+ * full-resolution {@link src}. Falls back to {@link src} when omitted.
3187
+ */
3188
+ thumbSrc?: string;
3178
3189
  alt?: string;
3179
3190
  /** Caption displayed in the lightbox bottom-left */
3180
3191
  caption?: string;
@@ -5429,10 +5440,13 @@ interface ReviewsSpotlightProps {
5429
5440
  className?: string;
5430
5441
  }
5431
5442
  /**
5432
- * ReviewsSpotlight — a single rotating testimonial. One large serif quote with
5433
- * Trustpilot stars, auto-advancing through `items` with clickable dots and a
5434
- * short crossfade. Honours `prefers-reduced-motion` (no auto-advance, instant
5435
- * swap). The dark band matches the rest of {@link NewHome}.
5443
+ * ReviewsSpotlight — a swipeable row of testimonials. Each review is a
5444
+ * full-width slide on a native horizontal scroll-snap track, so on touch it
5445
+ * can be flicked one review at a time; on desktop the dots advance it. One
5446
+ * large serif quote with Trustpilot stars, auto-advancing through `items` with
5447
+ * the pagination dots synced to scroll position. Autoplay pauses on
5448
+ * hover / focus / active swipe and is skipped entirely under
5449
+ * `prefers-reduced-motion`. The dark band matches the rest of {@link NewHome}.
5436
5450
  */
5437
5451
  declare function ReviewsSpotlight({ eyebrow, title, link, items, intervalMs, className, }: ReviewsSpotlightProps): react_jsx_runtime.JSX.Element | null;
5438
5452
 
package/dist/index.js CHANGED
@@ -12541,7 +12541,7 @@ function PhotoTile({
12541
12541
  onClick,
12542
12542
  showCredit = false
12543
12543
  }) {
12544
- var _a, _b;
12544
+ var _a, _b, _c;
12545
12545
  return /* @__PURE__ */ jsxs(
12546
12546
  "button",
12547
12547
  {
@@ -12556,8 +12556,8 @@ function PhotoTile({
12556
12556
  /* @__PURE__ */ jsx(
12557
12557
  Picture,
12558
12558
  {
12559
- src: photo.src,
12560
- alt: (_b = photo.alt) != null ? _b : `Photo ${index + 1}`,
12559
+ src: (_b = photo.thumbSrc) != null ? _b : photo.src,
12560
+ alt: (_c = photo.alt) != null ? _c : `Photo ${index + 1}`,
12561
12561
  title: photo.caption,
12562
12562
  className: "w-full h-full object-cover transition-transform duration-700 group-hover:scale-105",
12563
12563
  loading: "lazy"
@@ -12674,7 +12674,7 @@ function MasonryGallery({
12674
12674
  const visible = expanded || !hasMore ? photos : photos.slice(0, initialVisible);
12675
12675
  return /* @__PURE__ */ jsxs(Fragment, { children: [
12676
12676
  /* @__PURE__ */ jsx("div", { className: "columns-2 sm:columns-3 gap-1 [&>*]:break-inside-avoid [&>*]:mb-1", children: visible.map((p, i) => {
12677
- var _a, _b;
12677
+ var _a, _b, _c;
12678
12678
  return /* @__PURE__ */ jsxs(
12679
12679
  "button",
12680
12680
  {
@@ -12686,8 +12686,8 @@ function MasonryGallery({
12686
12686
  /* @__PURE__ */ jsx(
12687
12687
  Picture,
12688
12688
  {
12689
- src: p.src,
12690
- alt: (_b = p.alt) != null ? _b : `Photo ${i + 1}`,
12689
+ src: (_b = p.thumbSrc) != null ? _b : p.src,
12690
+ alt: (_c = p.alt) != null ? _c : `Photo ${i + 1}`,
12691
12691
  title: p.caption,
12692
12692
  className: "w-full h-auto object-cover transition-transform duration-700 group-hover:scale-105",
12693
12693
  loading: "lazy"
@@ -12715,7 +12715,7 @@ function FilmstripGallery({
12715
12715
  onOpen
12716
12716
  }) {
12717
12717
  return /* @__PURE__ */ jsx("div", { className: "overflow-x-auto flex gap-1 snap-x snap-mandatory pb-1 scrollbar-none", children: photos.map((p, i) => {
12718
- var _a, _b;
12718
+ var _a, _b, _c;
12719
12719
  return /* @__PURE__ */ jsxs(
12720
12720
  "button",
12721
12721
  {
@@ -12727,8 +12727,8 @@ function FilmstripGallery({
12727
12727
  /* @__PURE__ */ jsx(
12728
12728
  Picture,
12729
12729
  {
12730
- src: p.src,
12731
- alt: (_b = p.alt) != null ? _b : `Photo ${i + 1}`,
12730
+ src: (_b = p.thumbSrc) != null ? _b : p.src,
12731
+ alt: (_c = p.alt) != null ? _c : `Photo ${i + 1}`,
12732
12732
  title: p.caption,
12733
12733
  className: "h-full w-full object-cover transition-transform duration-700 group-hover:scale-105",
12734
12734
  loading: "lazy"
@@ -12782,7 +12782,7 @@ function FeaturedGallery({
12782
12782
  ] })
12783
12783
  ] }),
12784
12784
  expanded && extra.length > 0 && /* @__PURE__ */ jsx("div", { className: "mt-1.5 overflow-x-auto flex gap-1.5 snap-x snap-mandatory scrollbar-none rounded-xl overflow-hidden pb-0", children: extra.map((p, i) => {
12785
- var _a, _b;
12785
+ var _a, _b, _c;
12786
12786
  return /* @__PURE__ */ jsxs(
12787
12787
  "button",
12788
12788
  {
@@ -12794,8 +12794,8 @@ function FeaturedGallery({
12794
12794
  /* @__PURE__ */ jsx(
12795
12795
  Picture,
12796
12796
  {
12797
- src: p.src,
12798
- alt: (_b = p.alt) != null ? _b : `Photo ${i + 4}`,
12797
+ src: (_b = p.thumbSrc) != null ? _b : p.src,
12798
+ alt: (_c = p.alt) != null ? _c : `Photo ${i + 4}`,
12799
12799
  title: p.caption,
12800
12800
  className: "h-full w-full object-cover transition-transform duration-700 group-hover:scale-105",
12801
12801
  loading: "lazy"
@@ -20210,10 +20210,18 @@ function ReviewsSpotlight({
20210
20210
  intervalMs = 7e3,
20211
20211
  className
20212
20212
  }) {
20213
+ const trackRef = React20.useRef(null);
20213
20214
  const [index, setIndex] = React20.useState(0);
20214
- const [visible, setVisible] = React20.useState(true);
20215
20215
  const reduced = React20.useRef(false);
20216
- const swapRef = React20.useRef(null);
20216
+ const hoverRef = React20.useRef(false);
20217
+ const focusRef = React20.useRef(false);
20218
+ const interactRef = React20.useRef(false);
20219
+ const programmaticRef = React20.useRef(false);
20220
+ const rafRef = React20.useRef(null);
20221
+ const interactTimer = React20.useRef(null);
20222
+ const programmaticTimer = React20.useRef(
20223
+ null
20224
+ );
20217
20225
  React20.useEffect(() => {
20218
20226
  var _a;
20219
20227
  const mq = window.matchMedia("(prefers-reduced-motion: reduce)");
@@ -20227,56 +20235,107 @@ function ReviewsSpotlight({
20227
20235
  return (_a2 = mq.removeEventListener) == null ? void 0 : _a2.call(mq, "change", onChange);
20228
20236
  };
20229
20237
  }, []);
20230
- const goTo = React20.useCallback((resolve) => {
20231
- if (reduced.current) {
20232
- setIndex((i) => resolve(i));
20233
- return;
20238
+ const scrollToIndex = React20.useCallback((i) => {
20239
+ const el = trackRef.current;
20240
+ if (!el) return;
20241
+ const smooth = !reduced.current;
20242
+ programmaticRef.current = true;
20243
+ el.scrollTo({ left: i * el.clientWidth, behavior: smooth ? "smooth" : "auto" });
20244
+ if (programmaticTimer.current) clearTimeout(programmaticTimer.current);
20245
+ programmaticTimer.current = setTimeout(
20246
+ () => {
20247
+ programmaticRef.current = false;
20248
+ },
20249
+ smooth ? 600 : 50
20250
+ );
20251
+ }, []);
20252
+ const onScroll = React20.useCallback(() => {
20253
+ const el = trackRef.current;
20254
+ if (!el) return;
20255
+ if (rafRef.current) cancelAnimationFrame(rafRef.current);
20256
+ rafRef.current = requestAnimationFrame(() => {
20257
+ const i = Math.round(el.scrollLeft / Math.max(1, el.clientWidth));
20258
+ setIndex((prev) => prev === i ? prev : i);
20259
+ });
20260
+ if (!programmaticRef.current) {
20261
+ interactRef.current = true;
20262
+ if (interactTimer.current) clearTimeout(interactTimer.current);
20263
+ interactTimer.current = setTimeout(() => {
20264
+ interactRef.current = false;
20265
+ }, 1500);
20234
20266
  }
20235
- setVisible(false);
20236
- swapRef.current = setTimeout(() => {
20237
- setIndex((i) => resolve(i));
20238
- setVisible(true);
20239
- }, 260);
20240
20267
  }, []);
20241
20268
  React20.useEffect(() => {
20242
20269
  if (items.length <= 1 || intervalMs <= 0) return;
20243
20270
  const id = setInterval(() => {
20244
- if (!reduced.current) goTo((i) => (i + 1) % items.length);
20271
+ if (reduced.current) return;
20272
+ if (hoverRef.current || focusRef.current || interactRef.current) return;
20273
+ const el = trackRef.current;
20274
+ if (!el) return;
20275
+ const cur = Math.round(el.scrollLeft / Math.max(1, el.clientWidth));
20276
+ scrollToIndex((cur + 1) % items.length);
20245
20277
  }, intervalMs);
20246
- return () => {
20247
- clearInterval(id);
20248
- if (swapRef.current) clearTimeout(swapRef.current);
20249
- };
20250
- }, [items.length, intervalMs, goTo]);
20278
+ return () => clearInterval(id);
20279
+ }, [items.length, intervalMs, scrollToIndex]);
20280
+ React20.useEffect(
20281
+ () => () => {
20282
+ if (rafRef.current) cancelAnimationFrame(rafRef.current);
20283
+ if (interactTimer.current) clearTimeout(interactTimer.current);
20284
+ if (programmaticTimer.current) clearTimeout(programmaticTimer.current);
20285
+ },
20286
+ []
20287
+ );
20251
20288
  if (!items.length) return null;
20252
- const safe = Math.min(index, items.length - 1);
20253
- const review = items[safe];
20289
+ const active = Math.min(index, items.length - 1);
20254
20290
  return /* @__PURE__ */ jsx(
20255
20291
  "section",
20256
20292
  {
20257
20293
  className: cn(SURFACE_PRIMARY_9002, "py-24 text-white sm:py-32", className),
20294
+ onMouseEnter: () => {
20295
+ hoverRef.current = true;
20296
+ },
20297
+ onMouseLeave: () => {
20298
+ hoverRef.current = false;
20299
+ },
20300
+ onFocus: () => {
20301
+ focusRef.current = true;
20302
+ },
20303
+ onBlur: () => {
20304
+ focusRef.current = false;
20305
+ },
20258
20306
  children: /* @__PURE__ */ jsxs("div", { className: "mx-auto w-full max-w-3xl px-6 text-center sm:px-8", children: [
20259
20307
  eyebrow && /* @__PURE__ */ jsx("p", { className: "font-ui text-xs font-bold uppercase tracking-[0.22em] text-primary", children: eyebrow }),
20260
20308
  /* @__PURE__ */ jsx("h2", { className: "sr-only", children: title }),
20261
- /* @__PURE__ */ jsxs(
20309
+ /* @__PURE__ */ jsx(
20262
20310
  "div",
20263
20311
  {
20264
- className: cn(
20265
- "transition-opacity duration-300",
20266
- visible ? "opacity-100" : "opacity-0"
20267
- ),
20268
- children: [
20269
- /* @__PURE__ */ jsx("div", { className: "mt-8 flex justify-center", children: /* @__PURE__ */ jsx(TrustpilotStars, { stars: review.stars, className: "h-7 w-7" }) }),
20270
- /* @__PURE__ */ jsxs("blockquote", { className: "mx-auto mt-8 max-w-[20ch] font-sans text-3xl font-normal italic leading-[1.3] text-white sm:text-4xl", children: [
20271
- "\u201C",
20272
- review.quote,
20273
- "\u201D"
20274
- ] }),
20275
- (review.author || review.location) && /* @__PURE__ */ jsxs("div", { className: "mt-6", children: [
20276
- review.author && /* @__PURE__ */ jsx("p", { className: "font-ui text-base font-bold tracking-wide text-white", children: review.author }),
20277
- review.location && /* @__PURE__ */ jsx("p", { className: "mt-1 font-sans text-sm text-white/55", children: review.location })
20278
- ] })
20279
- ]
20312
+ ref: trackRef,
20313
+ onScroll,
20314
+ "aria-roledescription": "carousel",
20315
+ "aria-label": title,
20316
+ className: "flex snap-x snap-mandatory overflow-x-auto scrollbar-none",
20317
+ children: items.map((review, i) => /* @__PURE__ */ jsxs(
20318
+ "div",
20319
+ {
20320
+ role: "group",
20321
+ "aria-roledescription": "slide",
20322
+ "aria-label": `Review ${i + 1} of ${items.length}`,
20323
+ className: "w-full shrink-0 snap-start",
20324
+ children: [
20325
+ /* @__PURE__ */ jsx("div", { className: "mt-8 flex justify-center", children: /* @__PURE__ */ jsx(TrustpilotStars, { stars: review.stars, className: "h-7 w-7" }) }),
20326
+ /* @__PURE__ */ jsxs("blockquote", { className: "mx-auto mt-8 flex min-h-[15rem] max-w-[20ch] items-center justify-center font-sans text-3xl font-normal italic leading-[1.3] text-white sm:min-h-[18rem] sm:text-4xl", children: [
20327
+ "\u201C",
20328
+ review.quote,
20329
+ "\u201D"
20330
+ ] }),
20331
+ (review.author || review.location) && /* @__PURE__ */ jsxs("div", { className: "mt-6", children: [
20332
+ review.author && /* @__PURE__ */ jsx("p", { className: "font-ui text-base font-bold tracking-wide text-white", children: review.author }),
20333
+ review.location && /* @__PURE__ */ jsx("p", { className: "mt-1 font-sans text-sm text-white/55", children: review.location })
20334
+ ] })
20335
+ ]
20336
+ },
20337
+ i
20338
+ ))
20280
20339
  }
20281
20340
  ),
20282
20341
  items.length > 1 && /* @__PURE__ */ jsx("div", { className: "mt-10 flex justify-center gap-2.5", children: items.map((_, i) => /* @__PURE__ */ jsx(
@@ -20284,11 +20343,11 @@ function ReviewsSpotlight({
20284
20343
  {
20285
20344
  type: "button",
20286
20345
  "aria-label": `Show review ${i + 1} of ${items.length}`,
20287
- "aria-current": i === safe,
20288
- onClick: () => goTo(() => i),
20346
+ "aria-current": i === active,
20347
+ onClick: () => scrollToIndex(i),
20289
20348
  className: cn(
20290
20349
  "h-2 w-2 rounded-full transition-colors",
20291
- i === safe ? "bg-primary" : "bg-white/25 hover:bg-white/45"
20350
+ i === active ? "bg-primary" : "bg-white/25 hover:bg-white/45"
20292
20351
  )
20293
20352
  },
20294
20353
  i