@ship-it-ui/ui 0.0.11 → 0.0.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
@@ -1242,11 +1242,21 @@ interface CarouselProps<T = unknown> extends Omit<HTMLAttributes<HTMLDivElement>
1242
1242
  showArrows?: boolean;
1243
1243
  /**
1244
1244
  * Wrap arrows / dots / native swipe past the boundaries. Default `false`.
1245
- * When `true`, hidden clones of the first / last slide bracket the real
1246
- * items so swipe past the end jumps invisibly to the other side.
1247
- * `onIndexChange` still only emits real indices in `0..items.length - 1`.
1245
+ *
1246
+ * Variants:
1247
+ * - `"circular"` (or `true`): boundary arrow clicks smooth-scroll a
1248
+ * single slide width through a hidden clone of the opposite end, then
1249
+ * invisibly snap to the real twin. Feels like an endless reel — the
1250
+ * motion is always one slide, regardless of strip length.
1251
+ * - `"sweep"`: boundary arrow clicks smooth-scroll the full distance
1252
+ * across the strip back to the real first / last slide. The
1253
+ * transition reads as a wide arc across every item between.
1254
+ *
1255
+ * Native swipe past the edge always uses the clone-snap (independent of
1256
+ * variant). `onIndexChange` only emits real indices in
1257
+ * `0..items.length - 1`.
1248
1258
  */
1249
- loop?: boolean;
1259
+ loop?: boolean | 'circular' | 'sweep';
1250
1260
  /** Accessible label for the carousel region. */
1251
1261
  'aria-label'?: string;
1252
1262
  }
@@ -1583,13 +1593,13 @@ interface ListingCardProps extends Omit<HTMLAttributes<HTMLDivElement>, 'childre
1583
1593
  * browsing expects looping. Pass `false` to restore stop-at-end.
1584
1594
  */
1585
1595
  loop?: boolean;
1586
- /** Listing title — e.g. "2023 Tesla Model 3". */
1596
+ /** Listing title — e.g. "Sun-soaked cabin in Marin". */
1587
1597
  title: ReactNode;
1588
- /** Optional eyebrow text above the title (location, vehicle type). */
1598
+ /** Optional eyebrow text above the title (location, listing type). */
1589
1599
  eyebrow?: ReactNode;
1590
- /** Headline price (e.g. `89`). */
1600
+ /** Headline price (e.g. `189`). */
1591
1601
  price: ReactNode;
1592
- /** Price unit suffix (e.g. `/day`). */
1602
+ /** Price unit suffix (e.g. `/night`). */
1593
1603
  priceUnit?: ReactNode;
1594
1604
  /** Original price for sale strike-through. */
1595
1605
  originalPrice?: ReactNode;
@@ -1681,7 +1691,7 @@ interface ListingCardProps extends Omit<HTMLAttributes<HTMLDivElement>, 'childre
1681
1691
  footer: string;
1682
1692
  /** Price text. */
1683
1693
  price: string;
1684
- /** Price unit (e.g. `/day`). */
1694
+ /** Price unit (e.g. `/night`). */
1685
1695
  priceUnit: string;
1686
1696
  /** CTA button (spec variant). */
1687
1697
  cta: string;
@@ -1744,9 +1754,9 @@ interface ListingDetailProps {
1744
1754
  * drives both surfaces.
1745
1755
  */
1746
1756
  loop?: boolean;
1747
- /** Listing title — e.g. "2023 Tesla Model 3". */
1757
+ /** Listing title — e.g. "Sun-soaked cabin in Marin". */
1748
1758
  title: ReactNode;
1749
- /** Optional eyebrow above the title — vehicle type, location. */
1759
+ /** Optional eyebrow above the title — listing type, location. */
1750
1760
  eyebrow?: ReactNode;
1751
1761
  /** Long-form description body. */
1752
1762
  description?: ReactNode;
@@ -1754,9 +1764,9 @@ interface ListingDetailProps {
1754
1764
  rating?: number;
1755
1765
  /** Total review count, shown next to the rating. */
1756
1766
  reviewCount?: number;
1757
- /** Headline price (e.g. `$89`). */
1767
+ /** Headline price (e.g. `$189`). */
1758
1768
  price: ReactNode;
1759
- /** Suffix after the price (e.g. `/day`). */
1769
+ /** Suffix after the price (e.g. `/night`). */
1760
1770
  priceUnit?: ReactNode;
1761
1771
  /** Original price for a strike-through; renders only when set. */
1762
1772
  originalPrice?: ReactNode;
package/dist/index.d.ts CHANGED
@@ -1242,11 +1242,21 @@ interface CarouselProps<T = unknown> extends Omit<HTMLAttributes<HTMLDivElement>
1242
1242
  showArrows?: boolean;
1243
1243
  /**
1244
1244
  * Wrap arrows / dots / native swipe past the boundaries. Default `false`.
1245
- * When `true`, hidden clones of the first / last slide bracket the real
1246
- * items so swipe past the end jumps invisibly to the other side.
1247
- * `onIndexChange` still only emits real indices in `0..items.length - 1`.
1245
+ *
1246
+ * Variants:
1247
+ * - `"circular"` (or `true`): boundary arrow clicks smooth-scroll a
1248
+ * single slide width through a hidden clone of the opposite end, then
1249
+ * invisibly snap to the real twin. Feels like an endless reel — the
1250
+ * motion is always one slide, regardless of strip length.
1251
+ * - `"sweep"`: boundary arrow clicks smooth-scroll the full distance
1252
+ * across the strip back to the real first / last slide. The
1253
+ * transition reads as a wide arc across every item between.
1254
+ *
1255
+ * Native swipe past the edge always uses the clone-snap (independent of
1256
+ * variant). `onIndexChange` only emits real indices in
1257
+ * `0..items.length - 1`.
1248
1258
  */
1249
- loop?: boolean;
1259
+ loop?: boolean | 'circular' | 'sweep';
1250
1260
  /** Accessible label for the carousel region. */
1251
1261
  'aria-label'?: string;
1252
1262
  }
@@ -1583,13 +1593,13 @@ interface ListingCardProps extends Omit<HTMLAttributes<HTMLDivElement>, 'childre
1583
1593
  * browsing expects looping. Pass `false` to restore stop-at-end.
1584
1594
  */
1585
1595
  loop?: boolean;
1586
- /** Listing title — e.g. "2023 Tesla Model 3". */
1596
+ /** Listing title — e.g. "Sun-soaked cabin in Marin". */
1587
1597
  title: ReactNode;
1588
- /** Optional eyebrow text above the title (location, vehicle type). */
1598
+ /** Optional eyebrow text above the title (location, listing type). */
1589
1599
  eyebrow?: ReactNode;
1590
- /** Headline price (e.g. `89`). */
1600
+ /** Headline price (e.g. `189`). */
1591
1601
  price: ReactNode;
1592
- /** Price unit suffix (e.g. `/day`). */
1602
+ /** Price unit suffix (e.g. `/night`). */
1593
1603
  priceUnit?: ReactNode;
1594
1604
  /** Original price for sale strike-through. */
1595
1605
  originalPrice?: ReactNode;
@@ -1681,7 +1691,7 @@ interface ListingCardProps extends Omit<HTMLAttributes<HTMLDivElement>, 'childre
1681
1691
  footer: string;
1682
1692
  /** Price text. */
1683
1693
  price: string;
1684
- /** Price unit (e.g. `/day`). */
1694
+ /** Price unit (e.g. `/night`). */
1685
1695
  priceUnit: string;
1686
1696
  /** CTA button (spec variant). */
1687
1697
  cta: string;
@@ -1744,9 +1754,9 @@ interface ListingDetailProps {
1744
1754
  * drives both surfaces.
1745
1755
  */
1746
1756
  loop?: boolean;
1747
- /** Listing title — e.g. "2023 Tesla Model 3". */
1757
+ /** Listing title — e.g. "Sun-soaked cabin in Marin". */
1748
1758
  title: ReactNode;
1749
- /** Optional eyebrow above the title — vehicle type, location. */
1759
+ /** Optional eyebrow above the title — listing type, location. */
1750
1760
  eyebrow?: ReactNode;
1751
1761
  /** Long-form description body. */
1752
1762
  description?: ReactNode;
@@ -1754,9 +1764,9 @@ interface ListingDetailProps {
1754
1764
  rating?: number;
1755
1765
  /** Total review count, shown next to the rating. */
1756
1766
  reviewCount?: number;
1757
- /** Headline price (e.g. `$89`). */
1767
+ /** Headline price (e.g. `$189`). */
1758
1768
  price: ReactNode;
1759
- /** Suffix after the price (e.g. `/day`). */
1769
+ /** Suffix after the price (e.g. `/night`). */
1760
1770
  priceUnit?: ReactNode;
1761
1771
  /** Original price for a strike-through; renders only when set. */
1762
1772
  originalPrice?: ReactNode;
package/dist/index.js CHANGED
@@ -3100,7 +3100,8 @@ var Carousel = forwardRef44(function Carousel2({
3100
3100
  ...props
3101
3101
  }, ref) {
3102
3102
  const N = items.length;
3103
- const isLooping = loop && N > 1;
3103
+ const loopMode = !loop ? null : loop === true ? "circular" : loop;
3104
+ const isLooping = loopMode !== null && N > 1;
3104
3105
  const [active, setActive] = useControllableState({
3105
3106
  value: indexProp,
3106
3107
  defaultValue: defaultIndex ?? 0,
@@ -3108,6 +3109,7 @@ var Carousel = forwardRef44(function Carousel2({
3108
3109
  });
3109
3110
  const viewportRef = useRef8(null);
3110
3111
  const internalScrollRef = useRef8(false);
3112
+ const wrapInProgressRef = useRef8(false);
3111
3113
  const activeIdx = active ?? 0;
3112
3114
  const domIndexFor = useCallback9((real) => isLooping ? real + 1 : real, [isLooping]);
3113
3115
  const goTo = useCallback9(
@@ -3116,14 +3118,18 @@ var Carousel = forwardRef44(function Carousel2({
3116
3118
  setActive(next);
3117
3119
  const node = viewportRef.current;
3118
3120
  if (node) {
3119
- const slide = node.children[domIndexFor(next)];
3121
+ const isNextWrap = loopMode === "circular" && activeIdx === N - 1 && i === activeIdx + 1;
3122
+ const isPrevWrap = loopMode === "circular" && activeIdx === 0 && i === activeIdx - 1;
3123
+ const targetDom = isNextWrap ? N + 1 : isPrevWrap ? 0 : domIndexFor(next);
3124
+ const slide = node.children[targetDom];
3120
3125
  if (slide) {
3121
3126
  internalScrollRef.current = true;
3127
+ if (isNextWrap || isPrevWrap) wrapInProgressRef.current = true;
3122
3128
  slide.scrollIntoView({ behavior: "smooth", block: "nearest", inline: "start" });
3123
3129
  }
3124
3130
  }
3125
3131
  },
3126
- [N, isLooping, domIndexFor, setActive]
3132
+ [N, isLooping, loopMode, domIndexFor, setActive, activeIdx]
3127
3133
  );
3128
3134
  useEffect8(() => {
3129
3135
  const node = viewportRef.current;
@@ -3137,23 +3143,28 @@ var Carousel = forwardRef44(function Carousel2({
3137
3143
  return;
3138
3144
  }
3139
3145
  if (domIdx === 0) {
3146
+ if (wrapInProgressRef.current && node.scrollLeft > 1) return;
3140
3147
  const realTwin = node.children[N];
3141
3148
  if (realTwin) {
3142
3149
  internalScrollRef.current = true;
3143
3150
  realTwin.scrollIntoView({ behavior: "instant", block: "nearest", inline: "start" });
3144
3151
  }
3145
3152
  if (activeIdx !== N - 1) setActive(N - 1);
3153
+ wrapInProgressRef.current = false;
3146
3154
  return;
3147
3155
  }
3148
3156
  if (domIdx === N + 1) {
3157
+ if (wrapInProgressRef.current && node.scrollLeft < (N + 1) * width - 1) return;
3149
3158
  const realTwin = node.children[1];
3150
3159
  if (realTwin) {
3151
3160
  internalScrollRef.current = true;
3152
3161
  realTwin.scrollIntoView({ behavior: "instant", block: "nearest", inline: "start" });
3153
3162
  }
3154
3163
  if (activeIdx !== 0) setActive(0);
3164
+ wrapInProgressRef.current = false;
3155
3165
  return;
3156
3166
  }
3167
+ if (wrapInProgressRef.current) return;
3157
3168
  const realIdx = domIdx - 1;
3158
3169
  if (realIdx !== activeIdx) setActive(realIdx);
3159
3170
  };