@ship-it-ui/ui 0.0.10 → 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.cjs +139 -37
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +50 -11
- package/dist/index.d.ts +50 -11
- package/dist/index.js +140 -38
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.d.cts
CHANGED
|
@@ -1218,7 +1218,8 @@ declare const Crumb: react.ForwardRefExoticComponent<CrumbProps & react.RefAttri
|
|
|
1218
1218
|
* behavior; no library.
|
|
1219
1219
|
*
|
|
1220
1220
|
* Pass an array of `items` and a `renderItem` function — the carousel
|
|
1221
|
-
* handles snapping, active-index tracking, and keyboard nav.
|
|
1221
|
+
* handles snapping, active-index tracking, and keyboard nav. Set
|
|
1222
|
+
* `loop` to make arrows / dots / native swipe wrap continuously.
|
|
1222
1223
|
*/
|
|
1223
1224
|
interface CarouselProps<T = unknown> extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
|
|
1224
1225
|
/** Slide data. */
|
|
@@ -1239,6 +1240,23 @@ interface CarouselProps<T = unknown> extends Omit<HTMLAttributes<HTMLDivElement>
|
|
|
1239
1240
|
showDots?: boolean;
|
|
1240
1241
|
/** When false, hides the prev/next arrows. Default `true`. */
|
|
1241
1242
|
showArrows?: boolean;
|
|
1243
|
+
/**
|
|
1244
|
+
* Wrap arrows / dots / native swipe past the boundaries. Default `false`.
|
|
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`.
|
|
1258
|
+
*/
|
|
1259
|
+
loop?: boolean | 'circular' | 'sweep';
|
|
1242
1260
|
/** Accessible label for the carousel region. */
|
|
1243
1261
|
'aria-label'?: string;
|
|
1244
1262
|
}
|
|
@@ -1495,7 +1513,8 @@ declare const DateRangePicker: react.ForwardRefExoticComponent<DateRangePickerPr
|
|
|
1495
1513
|
/**
|
|
1496
1514
|
* Lightbox — fullscreen photo viewer. Built on Radix Dialog (reuses the
|
|
1497
1515
|
* focus trap, portal, and escape-to-close). Adds keyboard ←/→ navigation
|
|
1498
|
-
* between items and a counter overlay.
|
|
1516
|
+
* between items and a counter overlay. Set `loop` to wrap navigation
|
|
1517
|
+
* past the boundaries.
|
|
1499
1518
|
*/
|
|
1500
1519
|
interface LightboxProps {
|
|
1501
1520
|
open?: boolean;
|
|
@@ -1511,6 +1530,13 @@ interface LightboxProps {
|
|
|
1511
1530
|
defaultIndex?: number;
|
|
1512
1531
|
/** Fires when the index changes. */
|
|
1513
1532
|
onIndexChange?: (index: number) => void;
|
|
1533
|
+
/**
|
|
1534
|
+
* Wrap prev / next (buttons and ←/→ keys) past the boundaries. Default
|
|
1535
|
+
* `false`. When `true`, "next" on the last item goes to the first and
|
|
1536
|
+
* vice versa, and the arrow buttons never disable while there's more
|
|
1537
|
+
* than one item.
|
|
1538
|
+
*/
|
|
1539
|
+
loop?: boolean;
|
|
1514
1540
|
/** Accessible title (visually hidden). */
|
|
1515
1541
|
title?: ReactNode;
|
|
1516
1542
|
}
|
|
@@ -1561,13 +1587,19 @@ interface ListingCardProps extends Omit<HTMLAttributes<HTMLDivElement>, 'childre
|
|
|
1561
1587
|
* placeholders that follow `currentColor`) or non-image slides.
|
|
1562
1588
|
*/
|
|
1563
1589
|
renderPhoto?: (src: string, index: number) => ReactNode;
|
|
1564
|
-
/**
|
|
1590
|
+
/**
|
|
1591
|
+
* Wrap the photo carousel past the boundaries (next from the last
|
|
1592
|
+
* photo goes to the first). Default `true` — marketplace photo
|
|
1593
|
+
* browsing expects looping. Pass `false` to restore stop-at-end.
|
|
1594
|
+
*/
|
|
1595
|
+
loop?: boolean;
|
|
1596
|
+
/** Listing title — e.g. "Sun-soaked cabin in Marin". */
|
|
1565
1597
|
title: ReactNode;
|
|
1566
|
-
/** Optional eyebrow text above the title (location,
|
|
1598
|
+
/** Optional eyebrow text above the title (location, listing type). */
|
|
1567
1599
|
eyebrow?: ReactNode;
|
|
1568
|
-
/** Headline price (e.g. `
|
|
1600
|
+
/** Headline price (e.g. `189`). */
|
|
1569
1601
|
price: ReactNode;
|
|
1570
|
-
/** Price unit suffix (e.g. `/
|
|
1602
|
+
/** Price unit suffix (e.g. `/night`). */
|
|
1571
1603
|
priceUnit?: ReactNode;
|
|
1572
1604
|
/** Original price for sale strike-through. */
|
|
1573
1605
|
originalPrice?: ReactNode;
|
|
@@ -1659,7 +1691,7 @@ interface ListingCardProps extends Omit<HTMLAttributes<HTMLDivElement>, 'childre
|
|
|
1659
1691
|
footer: string;
|
|
1660
1692
|
/** Price text. */
|
|
1661
1693
|
price: string;
|
|
1662
|
-
/** Price unit (e.g. `/
|
|
1694
|
+
/** Price unit (e.g. `/night`). */
|
|
1663
1695
|
priceUnit: string;
|
|
1664
1696
|
/** CTA button (spec variant). */
|
|
1665
1697
|
cta: string;
|
|
@@ -1715,9 +1747,16 @@ interface ListingDetailProps {
|
|
|
1715
1747
|
* placeholders or non-image slides.
|
|
1716
1748
|
*/
|
|
1717
1749
|
renderPhoto?: (src: string, index: number, mode: 'gallery' | 'lightbox') => ReactNode;
|
|
1718
|
-
/**
|
|
1750
|
+
/**
|
|
1751
|
+
* Wrap the gallery carousel and the fullscreen lightbox past the
|
|
1752
|
+
* boundaries (next from the last photo goes to the first). Default
|
|
1753
|
+
* `true` — marketplace photo browsing expects looping. One prop
|
|
1754
|
+
* drives both surfaces.
|
|
1755
|
+
*/
|
|
1756
|
+
loop?: boolean;
|
|
1757
|
+
/** Listing title — e.g. "Sun-soaked cabin in Marin". */
|
|
1719
1758
|
title: ReactNode;
|
|
1720
|
-
/** Optional eyebrow above the title —
|
|
1759
|
+
/** Optional eyebrow above the title — listing type, location. */
|
|
1721
1760
|
eyebrow?: ReactNode;
|
|
1722
1761
|
/** Long-form description body. */
|
|
1723
1762
|
description?: ReactNode;
|
|
@@ -1725,9 +1764,9 @@ interface ListingDetailProps {
|
|
|
1725
1764
|
rating?: number;
|
|
1726
1765
|
/** Total review count, shown next to the rating. */
|
|
1727
1766
|
reviewCount?: number;
|
|
1728
|
-
/** Headline price (e.g. `$
|
|
1767
|
+
/** Headline price (e.g. `$189`). */
|
|
1729
1768
|
price: ReactNode;
|
|
1730
|
-
/** Suffix after the price (e.g. `/
|
|
1769
|
+
/** Suffix after the price (e.g. `/night`). */
|
|
1731
1770
|
priceUnit?: ReactNode;
|
|
1732
1771
|
/** Original price for a strike-through; renders only when set. */
|
|
1733
1772
|
originalPrice?: ReactNode;
|
package/dist/index.d.ts
CHANGED
|
@@ -1218,7 +1218,8 @@ declare const Crumb: react.ForwardRefExoticComponent<CrumbProps & react.RefAttri
|
|
|
1218
1218
|
* behavior; no library.
|
|
1219
1219
|
*
|
|
1220
1220
|
* Pass an array of `items` and a `renderItem` function — the carousel
|
|
1221
|
-
* handles snapping, active-index tracking, and keyboard nav.
|
|
1221
|
+
* handles snapping, active-index tracking, and keyboard nav. Set
|
|
1222
|
+
* `loop` to make arrows / dots / native swipe wrap continuously.
|
|
1222
1223
|
*/
|
|
1223
1224
|
interface CarouselProps<T = unknown> extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
|
|
1224
1225
|
/** Slide data. */
|
|
@@ -1239,6 +1240,23 @@ interface CarouselProps<T = unknown> extends Omit<HTMLAttributes<HTMLDivElement>
|
|
|
1239
1240
|
showDots?: boolean;
|
|
1240
1241
|
/** When false, hides the prev/next arrows. Default `true`. */
|
|
1241
1242
|
showArrows?: boolean;
|
|
1243
|
+
/**
|
|
1244
|
+
* Wrap arrows / dots / native swipe past the boundaries. Default `false`.
|
|
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`.
|
|
1258
|
+
*/
|
|
1259
|
+
loop?: boolean | 'circular' | 'sweep';
|
|
1242
1260
|
/** Accessible label for the carousel region. */
|
|
1243
1261
|
'aria-label'?: string;
|
|
1244
1262
|
}
|
|
@@ -1495,7 +1513,8 @@ declare const DateRangePicker: react.ForwardRefExoticComponent<DateRangePickerPr
|
|
|
1495
1513
|
/**
|
|
1496
1514
|
* Lightbox — fullscreen photo viewer. Built on Radix Dialog (reuses the
|
|
1497
1515
|
* focus trap, portal, and escape-to-close). Adds keyboard ←/→ navigation
|
|
1498
|
-
* between items and a counter overlay.
|
|
1516
|
+
* between items and a counter overlay. Set `loop` to wrap navigation
|
|
1517
|
+
* past the boundaries.
|
|
1499
1518
|
*/
|
|
1500
1519
|
interface LightboxProps {
|
|
1501
1520
|
open?: boolean;
|
|
@@ -1511,6 +1530,13 @@ interface LightboxProps {
|
|
|
1511
1530
|
defaultIndex?: number;
|
|
1512
1531
|
/** Fires when the index changes. */
|
|
1513
1532
|
onIndexChange?: (index: number) => void;
|
|
1533
|
+
/**
|
|
1534
|
+
* Wrap prev / next (buttons and ←/→ keys) past the boundaries. Default
|
|
1535
|
+
* `false`. When `true`, "next" on the last item goes to the first and
|
|
1536
|
+
* vice versa, and the arrow buttons never disable while there's more
|
|
1537
|
+
* than one item.
|
|
1538
|
+
*/
|
|
1539
|
+
loop?: boolean;
|
|
1514
1540
|
/** Accessible title (visually hidden). */
|
|
1515
1541
|
title?: ReactNode;
|
|
1516
1542
|
}
|
|
@@ -1561,13 +1587,19 @@ interface ListingCardProps extends Omit<HTMLAttributes<HTMLDivElement>, 'childre
|
|
|
1561
1587
|
* placeholders that follow `currentColor`) or non-image slides.
|
|
1562
1588
|
*/
|
|
1563
1589
|
renderPhoto?: (src: string, index: number) => ReactNode;
|
|
1564
|
-
/**
|
|
1590
|
+
/**
|
|
1591
|
+
* Wrap the photo carousel past the boundaries (next from the last
|
|
1592
|
+
* photo goes to the first). Default `true` — marketplace photo
|
|
1593
|
+
* browsing expects looping. Pass `false` to restore stop-at-end.
|
|
1594
|
+
*/
|
|
1595
|
+
loop?: boolean;
|
|
1596
|
+
/** Listing title — e.g. "Sun-soaked cabin in Marin". */
|
|
1565
1597
|
title: ReactNode;
|
|
1566
|
-
/** Optional eyebrow text above the title (location,
|
|
1598
|
+
/** Optional eyebrow text above the title (location, listing type). */
|
|
1567
1599
|
eyebrow?: ReactNode;
|
|
1568
|
-
/** Headline price (e.g. `
|
|
1600
|
+
/** Headline price (e.g. `189`). */
|
|
1569
1601
|
price: ReactNode;
|
|
1570
|
-
/** Price unit suffix (e.g. `/
|
|
1602
|
+
/** Price unit suffix (e.g. `/night`). */
|
|
1571
1603
|
priceUnit?: ReactNode;
|
|
1572
1604
|
/** Original price for sale strike-through. */
|
|
1573
1605
|
originalPrice?: ReactNode;
|
|
@@ -1659,7 +1691,7 @@ interface ListingCardProps extends Omit<HTMLAttributes<HTMLDivElement>, 'childre
|
|
|
1659
1691
|
footer: string;
|
|
1660
1692
|
/** Price text. */
|
|
1661
1693
|
price: string;
|
|
1662
|
-
/** Price unit (e.g. `/
|
|
1694
|
+
/** Price unit (e.g. `/night`). */
|
|
1663
1695
|
priceUnit: string;
|
|
1664
1696
|
/** CTA button (spec variant). */
|
|
1665
1697
|
cta: string;
|
|
@@ -1715,9 +1747,16 @@ interface ListingDetailProps {
|
|
|
1715
1747
|
* placeholders or non-image slides.
|
|
1716
1748
|
*/
|
|
1717
1749
|
renderPhoto?: (src: string, index: number, mode: 'gallery' | 'lightbox') => ReactNode;
|
|
1718
|
-
/**
|
|
1750
|
+
/**
|
|
1751
|
+
* Wrap the gallery carousel and the fullscreen lightbox past the
|
|
1752
|
+
* boundaries (next from the last photo goes to the first). Default
|
|
1753
|
+
* `true` — marketplace photo browsing expects looping. One prop
|
|
1754
|
+
* drives both surfaces.
|
|
1755
|
+
*/
|
|
1756
|
+
loop?: boolean;
|
|
1757
|
+
/** Listing title — e.g. "Sun-soaked cabin in Marin". */
|
|
1719
1758
|
title: ReactNode;
|
|
1720
|
-
/** Optional eyebrow above the title —
|
|
1759
|
+
/** Optional eyebrow above the title — listing type, location. */
|
|
1721
1760
|
eyebrow?: ReactNode;
|
|
1722
1761
|
/** Long-form description body. */
|
|
1723
1762
|
description?: ReactNode;
|
|
@@ -1725,9 +1764,9 @@ interface ListingDetailProps {
|
|
|
1725
1764
|
rating?: number;
|
|
1726
1765
|
/** Total review count, shown next to the rating. */
|
|
1727
1766
|
reviewCount?: number;
|
|
1728
|
-
/** Headline price (e.g. `$
|
|
1767
|
+
/** Headline price (e.g. `$189`). */
|
|
1729
1768
|
price: ReactNode;
|
|
1730
|
-
/** Suffix after the price (e.g. `/
|
|
1769
|
+
/** Suffix after the price (e.g. `/night`). */
|
|
1731
1770
|
priceUnit?: ReactNode;
|
|
1732
1771
|
/** Original price for a strike-through; renders only when set. */
|
|
1733
1772
|
originalPrice?: ReactNode;
|
package/dist/index.js
CHANGED
|
@@ -3080,6 +3080,7 @@ import {
|
|
|
3080
3080
|
forwardRef as forwardRef44,
|
|
3081
3081
|
useCallback as useCallback9,
|
|
3082
3082
|
useEffect as useEffect8,
|
|
3083
|
+
useLayoutEffect as useLayoutEffect2,
|
|
3083
3084
|
useRef as useRef8
|
|
3084
3085
|
} from "react";
|
|
3085
3086
|
import { Fragment, jsx as jsx45, jsxs as jsxs38 } from "react/jsx-runtime";
|
|
@@ -3093,27 +3094,42 @@ var Carousel = forwardRef44(function Carousel2({
|
|
|
3093
3094
|
aspectRatio = 16 / 10,
|
|
3094
3095
|
showDots = true,
|
|
3095
3096
|
showArrows = true,
|
|
3097
|
+
loop = false,
|
|
3096
3098
|
className,
|
|
3097
3099
|
"aria-label": ariaLabel = "Carousel",
|
|
3098
3100
|
...props
|
|
3099
3101
|
}, ref) {
|
|
3102
|
+
const N = items.length;
|
|
3103
|
+
const loopMode = !loop ? null : loop === true ? "circular" : loop;
|
|
3104
|
+
const isLooping = loopMode !== null && N > 1;
|
|
3100
3105
|
const [active, setActive] = useControllableState({
|
|
3101
3106
|
value: indexProp,
|
|
3102
3107
|
defaultValue: defaultIndex ?? 0,
|
|
3103
3108
|
onChange: onIndexChange
|
|
3104
3109
|
});
|
|
3105
3110
|
const viewportRef = useRef8(null);
|
|
3111
|
+
const internalScrollRef = useRef8(false);
|
|
3112
|
+
const wrapInProgressRef = useRef8(false);
|
|
3113
|
+
const activeIdx = active ?? 0;
|
|
3114
|
+
const domIndexFor = useCallback9((real) => isLooping ? real + 1 : real, [isLooping]);
|
|
3106
3115
|
const goTo = useCallback9(
|
|
3107
3116
|
(i) => {
|
|
3108
|
-
const
|
|
3109
|
-
setActive(
|
|
3117
|
+
const next = isLooping ? (i % N + N) % N : Math.max(0, Math.min(N - 1, i));
|
|
3118
|
+
setActive(next);
|
|
3110
3119
|
const node = viewportRef.current;
|
|
3111
3120
|
if (node) {
|
|
3112
|
-
const
|
|
3113
|
-
|
|
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];
|
|
3125
|
+
if (slide) {
|
|
3126
|
+
internalScrollRef.current = true;
|
|
3127
|
+
if (isNextWrap || isPrevWrap) wrapInProgressRef.current = true;
|
|
3128
|
+
slide.scrollIntoView({ behavior: "smooth", block: "nearest", inline: "start" });
|
|
3129
|
+
}
|
|
3114
3130
|
}
|
|
3115
3131
|
},
|
|
3116
|
-
[
|
|
3132
|
+
[N, isLooping, loopMode, domIndexFor, setActive, activeIdx]
|
|
3117
3133
|
);
|
|
3118
3134
|
useEffect8(() => {
|
|
3119
3135
|
const node = viewportRef.current;
|
|
@@ -3121,13 +3137,64 @@ var Carousel = forwardRef44(function Carousel2({
|
|
|
3121
3137
|
const onScroll = () => {
|
|
3122
3138
|
const width = node.clientWidth;
|
|
3123
3139
|
if (width === 0) return;
|
|
3124
|
-
const
|
|
3125
|
-
if (
|
|
3140
|
+
const domIdx = Math.round(node.scrollLeft / width);
|
|
3141
|
+
if (!isLooping) {
|
|
3142
|
+
if (domIdx !== activeIdx) setActive(domIdx);
|
|
3143
|
+
return;
|
|
3144
|
+
}
|
|
3145
|
+
if (domIdx === 0) {
|
|
3146
|
+
if (wrapInProgressRef.current && node.scrollLeft > 1) return;
|
|
3147
|
+
const realTwin = node.children[N];
|
|
3148
|
+
if (realTwin) {
|
|
3149
|
+
internalScrollRef.current = true;
|
|
3150
|
+
realTwin.scrollIntoView({ behavior: "instant", block: "nearest", inline: "start" });
|
|
3151
|
+
}
|
|
3152
|
+
if (activeIdx !== N - 1) setActive(N - 1);
|
|
3153
|
+
wrapInProgressRef.current = false;
|
|
3154
|
+
return;
|
|
3155
|
+
}
|
|
3156
|
+
if (domIdx === N + 1) {
|
|
3157
|
+
if (wrapInProgressRef.current && node.scrollLeft < (N + 1) * width - 1) return;
|
|
3158
|
+
const realTwin = node.children[1];
|
|
3159
|
+
if (realTwin) {
|
|
3160
|
+
internalScrollRef.current = true;
|
|
3161
|
+
realTwin.scrollIntoView({ behavior: "instant", block: "nearest", inline: "start" });
|
|
3162
|
+
}
|
|
3163
|
+
if (activeIdx !== 0) setActive(0);
|
|
3164
|
+
wrapInProgressRef.current = false;
|
|
3165
|
+
return;
|
|
3166
|
+
}
|
|
3167
|
+
if (wrapInProgressRef.current) return;
|
|
3168
|
+
const realIdx = domIdx - 1;
|
|
3169
|
+
if (realIdx !== activeIdx) setActive(realIdx);
|
|
3126
3170
|
};
|
|
3127
3171
|
node.addEventListener("scroll", onScroll, { passive: true });
|
|
3128
3172
|
return () => node.removeEventListener("scroll", onScroll);
|
|
3129
|
-
}, [
|
|
3130
|
-
|
|
3173
|
+
}, [activeIdx, isLooping, N, setActive]);
|
|
3174
|
+
useEffect8(() => {
|
|
3175
|
+
if (internalScrollRef.current) {
|
|
3176
|
+
internalScrollRef.current = false;
|
|
3177
|
+
return;
|
|
3178
|
+
}
|
|
3179
|
+
const node = viewportRef.current;
|
|
3180
|
+
if (!node) return;
|
|
3181
|
+
const width = node.clientWidth;
|
|
3182
|
+
if (width === 0) return;
|
|
3183
|
+
const targetDom = domIndexFor(activeIdx);
|
|
3184
|
+
const currentDom = Math.round(node.scrollLeft / width);
|
|
3185
|
+
if (currentDom === targetDom) return;
|
|
3186
|
+
const slide = node.children[targetDom];
|
|
3187
|
+
slide?.scrollIntoView({ behavior: "instant", block: "nearest", inline: "start" });
|
|
3188
|
+
}, [activeIdx, domIndexFor]);
|
|
3189
|
+
useLayoutEffect2(() => {
|
|
3190
|
+
if (!isLooping) return;
|
|
3191
|
+
const node = viewportRef.current;
|
|
3192
|
+
if (!node) return;
|
|
3193
|
+
const slide = node.children[domIndexFor(activeIdx)];
|
|
3194
|
+
if (!slide) return;
|
|
3195
|
+
internalScrollRef.current = true;
|
|
3196
|
+
slide.scrollIntoView({ behavior: "instant", block: "nearest", inline: "start" });
|
|
3197
|
+
}, [isLooping]);
|
|
3131
3198
|
return /* @__PURE__ */ jsxs38(
|
|
3132
3199
|
"div",
|
|
3133
3200
|
{
|
|
@@ -3139,34 +3206,58 @@ var Carousel = forwardRef44(function Carousel2({
|
|
|
3139
3206
|
...props,
|
|
3140
3207
|
children: [
|
|
3141
3208
|
/* @__PURE__ */ jsxs38("div", { className: "relative overflow-hidden rounded-md", children: [
|
|
3142
|
-
/* @__PURE__ */
|
|
3209
|
+
/* @__PURE__ */ jsxs38(
|
|
3143
3210
|
"div",
|
|
3144
3211
|
{
|
|
3145
3212
|
ref: viewportRef,
|
|
3146
3213
|
className: "flex w-full snap-x snap-mandatory overflow-x-auto scroll-smooth [scrollbar-width:none] [&::-webkit-scrollbar]:hidden",
|
|
3147
3214
|
"aria-live": "polite",
|
|
3148
|
-
children:
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3215
|
+
children: [
|
|
3216
|
+
isLooping && /* @__PURE__ */ jsx45(
|
|
3217
|
+
"div",
|
|
3218
|
+
{
|
|
3219
|
+
"aria-hidden": "true",
|
|
3220
|
+
tabIndex: -1,
|
|
3221
|
+
className: "w-full shrink-0 snap-start",
|
|
3222
|
+
style: { aspectRatio: String(aspectRatio) },
|
|
3223
|
+
children: renderItem(items[N - 1], N - 1)
|
|
3224
|
+
},
|
|
3225
|
+
"clone-start"
|
|
3226
|
+
),
|
|
3227
|
+
items.map((item, i) => /* @__PURE__ */ jsx45(
|
|
3228
|
+
"div",
|
|
3229
|
+
{
|
|
3230
|
+
className: "w-full shrink-0 snap-start",
|
|
3231
|
+
style: { aspectRatio: String(aspectRatio) },
|
|
3232
|
+
role: "group",
|
|
3233
|
+
"aria-roledescription": "slide",
|
|
3234
|
+
"aria-label": `${i + 1} of ${N}`,
|
|
3235
|
+
children: renderItem(item, i)
|
|
3236
|
+
},
|
|
3237
|
+
i
|
|
3238
|
+
)),
|
|
3239
|
+
isLooping && /* @__PURE__ */ jsx45(
|
|
3240
|
+
"div",
|
|
3241
|
+
{
|
|
3242
|
+
"aria-hidden": "true",
|
|
3243
|
+
tabIndex: -1,
|
|
3244
|
+
className: "w-full shrink-0 snap-start",
|
|
3245
|
+
style: { aspectRatio: String(aspectRatio) },
|
|
3246
|
+
children: renderItem(items[0], 0)
|
|
3247
|
+
},
|
|
3248
|
+
"clone-end"
|
|
3249
|
+
)
|
|
3250
|
+
]
|
|
3160
3251
|
}
|
|
3161
3252
|
),
|
|
3162
|
-
showArrows &&
|
|
3253
|
+
showArrows && N > 1 && /* @__PURE__ */ jsxs38(Fragment, { children: [
|
|
3163
3254
|
/* @__PURE__ */ jsx45(
|
|
3164
3255
|
"button",
|
|
3165
3256
|
{
|
|
3166
3257
|
type: "button",
|
|
3167
3258
|
"aria-label": "Previous slide",
|
|
3168
3259
|
onClick: () => goTo(activeIdx - 1),
|
|
3169
|
-
disabled: activeIdx === 0,
|
|
3260
|
+
disabled: !isLooping && activeIdx === 0,
|
|
3170
3261
|
className: "bg-panel/85 border-border text-text hover:bg-panel absolute top-1/2 left-2 inline-grid h-9 w-9 -translate-y-1/2 cursor-pointer place-items-center rounded-full border shadow-md backdrop-blur disabled:cursor-not-allowed disabled:opacity-40",
|
|
3171
3262
|
children: /* @__PURE__ */ jsx45(IconGlyph4, { name: "caretLeft", size: 16 })
|
|
3172
3263
|
}
|
|
@@ -3177,13 +3268,13 @@ var Carousel = forwardRef44(function Carousel2({
|
|
|
3177
3268
|
type: "button",
|
|
3178
3269
|
"aria-label": "Next slide",
|
|
3179
3270
|
onClick: () => goTo(activeIdx + 1),
|
|
3180
|
-
disabled: activeIdx ===
|
|
3271
|
+
disabled: !isLooping && activeIdx === N - 1,
|
|
3181
3272
|
className: "bg-panel/85 border-border text-text hover:bg-panel absolute top-1/2 right-2 inline-grid h-9 w-9 -translate-y-1/2 cursor-pointer place-items-center rounded-full border shadow-md backdrop-blur disabled:cursor-not-allowed disabled:opacity-40",
|
|
3182
3273
|
children: /* @__PURE__ */ jsx45(IconGlyph4, { name: "caretRight", size: 16 })
|
|
3183
3274
|
}
|
|
3184
3275
|
)
|
|
3185
3276
|
] }),
|
|
3186
|
-
showDots &&
|
|
3277
|
+
showDots && N > 1 && /*
|
|
3187
3278
|
* Plain `<button>` + `aria-current` rather than the tabs pattern
|
|
3188
3279
|
* (`role="tablist" / "tab"`). The APG carousel pattern recommends
|
|
3189
3280
|
* this lighter semantic; the viewport's `aria-live="polite"`
|
|
@@ -4256,19 +4347,28 @@ var Lightbox = forwardRef50(function Lightbox2({
|
|
|
4256
4347
|
index,
|
|
4257
4348
|
defaultIndex,
|
|
4258
4349
|
onIndexChange,
|
|
4350
|
+
loop = false,
|
|
4259
4351
|
title = "Photo viewer"
|
|
4260
4352
|
}, ref) {
|
|
4353
|
+
const N = items.length;
|
|
4354
|
+
const isLooping = loop && N > 1;
|
|
4261
4355
|
const [active, setActive] = useControllableState({
|
|
4262
4356
|
value: index,
|
|
4263
4357
|
defaultValue: defaultIndex ?? 0,
|
|
4264
4358
|
onChange: onIndexChange
|
|
4265
4359
|
});
|
|
4266
4360
|
const goPrev = useCallback11(() => {
|
|
4267
|
-
setActive((prev) =>
|
|
4268
|
-
|
|
4361
|
+
setActive((prev) => {
|
|
4362
|
+
const p = prev ?? 0;
|
|
4363
|
+
return isLooping ? (p - 1 + N) % N : Math.max(0, p - 1);
|
|
4364
|
+
});
|
|
4365
|
+
}, [setActive, isLooping, N]);
|
|
4269
4366
|
const goNext = useCallback11(() => {
|
|
4270
|
-
setActive((prev) =>
|
|
4271
|
-
|
|
4367
|
+
setActive((prev) => {
|
|
4368
|
+
const p = prev ?? 0;
|
|
4369
|
+
return isLooping ? (p + 1) % N : Math.min(N - 1, p + 1);
|
|
4370
|
+
});
|
|
4371
|
+
}, [setActive, isLooping, N]);
|
|
4272
4372
|
const onKey = useCallback11(
|
|
4273
4373
|
(e) => {
|
|
4274
4374
|
if (e.key === "ArrowLeft") {
|
|
@@ -4308,7 +4408,7 @@ var Lightbox = forwardRef50(function Lightbox2({
|
|
|
4308
4408
|
type: "button",
|
|
4309
4409
|
"aria-label": "Previous photo",
|
|
4310
4410
|
onClick: goPrev,
|
|
4311
|
-
disabled: activeIdx === 0,
|
|
4411
|
+
disabled: !isLooping && activeIdx === 0,
|
|
4312
4412
|
className: "absolute top-1/2 left-4 inline-grid h-11 w-11 -translate-y-1/2 cursor-pointer place-items-center rounded-full bg-white/10 text-white hover:bg-white/20 disabled:cursor-not-allowed disabled:opacity-40",
|
|
4313
4413
|
children: /* @__PURE__ */ jsx52(IconGlyph6, { name: "caretLeft", size: 20 })
|
|
4314
4414
|
}
|
|
@@ -4319,7 +4419,7 @@ var Lightbox = forwardRef50(function Lightbox2({
|
|
|
4319
4419
|
type: "button",
|
|
4320
4420
|
"aria-label": "Next photo",
|
|
4321
4421
|
onClick: goNext,
|
|
4322
|
-
disabled: activeIdx ===
|
|
4422
|
+
disabled: !isLooping && activeIdx === N - 1,
|
|
4323
4423
|
className: "absolute top-1/2 right-4 inline-grid h-11 w-11 -translate-y-1/2 cursor-pointer place-items-center rounded-full bg-white/10 text-white hover:bg-white/20 disabled:cursor-not-allowed disabled:opacity-40",
|
|
4324
4424
|
children: /* @__PURE__ */ jsx52(IconGlyph6, { name: "caretRight", size: 20 })
|
|
4325
4425
|
}
|
|
@@ -4372,6 +4472,7 @@ var ListingCard = forwardRef51(function ListingCard2({
|
|
|
4372
4472
|
variant = "default",
|
|
4373
4473
|
photos,
|
|
4374
4474
|
renderPhoto,
|
|
4475
|
+
loop = true,
|
|
4375
4476
|
onClick,
|
|
4376
4477
|
hoverEffect,
|
|
4377
4478
|
title,
|
|
@@ -4418,6 +4519,7 @@ var ListingCard = forwardRef51(function ListingCard2({
|
|
|
4418
4519
|
Carousel,
|
|
4419
4520
|
{
|
|
4420
4521
|
items: photos,
|
|
4522
|
+
loop,
|
|
4421
4523
|
...isSpec ? {
|
|
4422
4524
|
index: photoIndex,
|
|
4423
4525
|
onIndexChange: setPhotoIndex,
|
|
@@ -4658,6 +4760,7 @@ var ListingDetail = forwardRef52(function ListingDetail2({
|
|
|
4658
4760
|
onOpenChange,
|
|
4659
4761
|
photos,
|
|
4660
4762
|
renderPhoto,
|
|
4763
|
+
loop = true,
|
|
4661
4764
|
title,
|
|
4662
4765
|
eyebrow,
|
|
4663
4766
|
description,
|
|
@@ -4727,6 +4830,7 @@ var ListingDetail = forwardRef52(function ListingDetail2({
|
|
|
4727
4830
|
items: photos,
|
|
4728
4831
|
index: galleryIndex,
|
|
4729
4832
|
onIndexChange: setGalleryIndex,
|
|
4833
|
+
loop,
|
|
4730
4834
|
...isSpec ? { showDots: false } : {},
|
|
4731
4835
|
"aria-label": typeof title === "string" ? `${title} photos` : "Listing photos",
|
|
4732
4836
|
renderItem: (src, i) => renderPhoto ? renderPhoto(src, i, "gallery") : /* @__PURE__ */ jsx54(
|
|
@@ -4972,6 +5076,7 @@ var ListingDetail = forwardRef52(function ListingDetail2({
|
|
|
4972
5076
|
items: photos,
|
|
4973
5077
|
index: galleryIndex,
|
|
4974
5078
|
onIndexChange: setGalleryIndex,
|
|
5079
|
+
loop,
|
|
4975
5080
|
title: lightboxTitle,
|
|
4976
5081
|
renderItem: (src, i) => renderPhoto ? renderPhoto(src, i, "lightbox") : /* @__PURE__ */ jsx54("img", { src, alt: "", className: "max-h-[88vh] max-w-[92vw] object-contain" })
|
|
4977
5082
|
}
|
|
@@ -7241,7 +7346,6 @@ Topbar.displayName = "Topbar";
|
|
|
7241
7346
|
import {
|
|
7242
7347
|
forwardRef as forwardRef79,
|
|
7243
7348
|
useCallback as useCallback16,
|
|
7244
|
-
useEffect as useEffect16,
|
|
7245
7349
|
useMemo as useMemo7,
|
|
7246
7350
|
useRef as useRef14,
|
|
7247
7351
|
useState as useState21
|
|
@@ -7287,11 +7391,9 @@ var Tree = forwardRef79(function Tree2({
|
|
|
7287
7391
|
return out;
|
|
7288
7392
|
}, [items, expandedSet]);
|
|
7289
7393
|
const [activeId, setActiveId] = useState21(null);
|
|
7290
|
-
|
|
7291
|
-
|
|
7292
|
-
|
|
7293
|
-
}
|
|
7294
|
-
}, [activeId, flatVisible]);
|
|
7394
|
+
if (activeId && !flatVisible.some((f) => f.id === activeId)) {
|
|
7395
|
+
setActiveId(null);
|
|
7396
|
+
}
|
|
7295
7397
|
const tabStopId = useMemo7(() => {
|
|
7296
7398
|
if (activeId && flatVisible.some((f) => f.id === activeId)) return activeId;
|
|
7297
7399
|
if (value && flatVisible.some((f) => f.id === value)) return value;
|