@trackunit/react-components 1.9.7 → 1.9.9

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/index.cjs.js CHANGED
@@ -1313,7 +1313,7 @@ const useElevatedState = (initialState, customState) => {
1313
1313
  * Custom hook to get the geometry of an element.
1314
1314
  * Size and position of the element relative to the viewport.
1315
1315
  */
1316
- const useGeometry = (ref) => {
1316
+ const useGeometry = (ref, { skip = false } = {}) => {
1317
1317
  const [geometry, setGeometry] = react.useState({
1318
1318
  width: 0,
1319
1319
  height: 0,
@@ -1326,7 +1326,7 @@ const useGeometry = (ref) => {
1326
1326
  });
1327
1327
  const resizeObserver = react.useRef(null);
1328
1328
  react.useEffect(() => {
1329
- if (!ref.current) {
1329
+ if (skip || !ref.current) {
1330
1330
  return;
1331
1331
  }
1332
1332
  const observe = () => {
@@ -1357,7 +1357,7 @@ const useGeometry = (ref) => {
1357
1357
  resizeObserver.current.disconnect();
1358
1358
  }
1359
1359
  };
1360
- }, [ref]);
1360
+ }, [ref, skip]);
1361
1361
  return geometry;
1362
1362
  };
1363
1363
 
@@ -1486,17 +1486,21 @@ const useModifierKey = ({ exclude = [] } = {}) => {
1486
1486
  /**
1487
1487
  * Custom hook to handle window resize events and provide the current window size.
1488
1488
  *
1489
+ * @param {UseResizeOptions} options - Options for the hook.
1489
1490
  * @returns {WindowSize} An object containing the current window height and width.
1490
1491
  */
1491
- const useResize = () => {
1492
+ const useResize = ({ skip = false } = {}) => {
1492
1493
  const [size, setSize] = react.useState(getWindowSize());
1493
1494
  react.useEffect(() => {
1495
+ if (skip) {
1496
+ return;
1497
+ }
1494
1498
  const handleResize = () => {
1495
1499
  setSize(getWindowSize());
1496
1500
  };
1497
1501
  window.addEventListener("resize", handleResize, false);
1498
1502
  return () => window.removeEventListener("resize", handleResize);
1499
- }, [setSize]);
1503
+ }, [setSize, skip]);
1500
1504
  return size;
1501
1505
  };
1502
1506
  /**
@@ -1521,36 +1525,73 @@ const getWindowSize = () => {
1521
1525
 
1522
1526
  const SCROLL_DEBOUNCE_TIME = 50;
1523
1527
  /**
1524
- * Hook for getting detecting scroll values.
1528
+ * Hook for detecting scroll values in horizontal or vertical direction.
1525
1529
  *
1526
1530
  * @param {useRef} elementRef - Ref hook holding the element that needs to be observed during scrolling
1527
- * @returns {object} An object containing if the element is scrollable, is at the top, is at the bottom, and its current scroll position.
1531
+ * @param {object} options - Options object containing direction and onScrollStateChange callback
1532
+ * @returns {object} An object containing if the element is scrollable, is at the beginning, is at the end, and its current scroll position.
1528
1533
  */
1529
- const useScrollDetection = (elementRef) => {
1534
+ const useScrollDetection = (elementRef, options) => {
1535
+ const { direction = "vertical", onScrollStateChange } = options ?? {};
1530
1536
  const [isScrollable, setIsScrollable] = react.useState(false);
1531
- const [isAtTop, setIsAtTop] = react.useState(true);
1532
- const [isAtBottom, setIsAtBottom] = react.useState(false);
1533
- const [scrollPosition, setScrollPosition] = react.useState({ top: 0, bottom: 0 });
1537
+ const [isAtBeginning, setIsAtBeginning] = react.useState(true);
1538
+ const [isAtEnd, setIsAtEnd] = react.useState(false);
1539
+ const [scrollPosition, setScrollPosition] = react.useState({ start: 0, end: 0 });
1534
1540
  const observerRef = react.useRef(null);
1541
+ const isFirstRender = useIsFirstRender();
1535
1542
  const checkScrollable = react.useCallback(() => {
1536
1543
  const element = elementRef?.current;
1537
1544
  if (!element) {
1538
1545
  return;
1539
1546
  }
1540
- const hasOverflow = element.scrollHeight > element.clientHeight;
1541
- setIsScrollable(hasOverflow);
1542
- }, [elementRef]);
1547
+ const hasOverflow = direction === "horizontal"
1548
+ ? element.scrollWidth > element.clientWidth
1549
+ : element.scrollHeight > element.clientHeight;
1550
+ setIsScrollable(prev => {
1551
+ if (prev !== hasOverflow) {
1552
+ // State will be updated, so we'll notify in the next effect
1553
+ return hasOverflow;
1554
+ }
1555
+ return prev;
1556
+ });
1557
+ }, [elementRef, direction]);
1543
1558
  const checkScrollPosition = react.useCallback(() => {
1544
1559
  const element = elementRef?.current;
1545
1560
  if (!element) {
1546
1561
  return;
1547
1562
  }
1548
- const { scrollTop, scrollHeight, clientHeight } = element;
1549
- setIsAtTop(scrollTop === 0);
1550
- setIsAtBottom(Math.abs(scrollHeight - scrollTop - clientHeight) <= 1);
1551
- setScrollPosition(prev => ({ ...prev, top: scrollTop, bottom: clientHeight - scrollTop }));
1552
- }, [elementRef]);
1563
+ if (direction === "horizontal") {
1564
+ const { scrollLeft, scrollWidth, clientWidth } = element;
1565
+ const newIsAtBeginning = scrollLeft === 0;
1566
+ const newIsAtEnd = Math.abs(scrollWidth - scrollLeft - clientWidth) <= 1;
1567
+ const newScrollPosition = { start: scrollLeft, end: clientWidth - scrollLeft };
1568
+ setIsAtBeginning(newIsAtBeginning);
1569
+ setIsAtEnd(newIsAtEnd);
1570
+ setScrollPosition(newScrollPosition);
1571
+ }
1572
+ else {
1573
+ const { scrollTop, scrollHeight, clientHeight } = element;
1574
+ const newIsAtBeginning = scrollTop === 0;
1575
+ const newIsAtEnd = Math.abs(scrollHeight - scrollTop - clientHeight) <= 1;
1576
+ const newScrollPosition = { start: scrollTop, end: clientHeight - scrollTop };
1577
+ setIsAtBeginning(newIsAtBeginning);
1578
+ setIsAtEnd(newIsAtEnd);
1579
+ setScrollPosition(newScrollPosition);
1580
+ }
1581
+ }, [elementRef, direction]);
1553
1582
  const debouncedCheckScrollPosition = usehooksTs.useDebounceCallback(checkScrollPosition, SCROLL_DEBOUNCE_TIME);
1583
+ // Notify about state changes whenever any state value changes
1584
+ react.useEffect(() => {
1585
+ if (isFirstRender) {
1586
+ return;
1587
+ }
1588
+ onScrollStateChange?.({
1589
+ isScrollable,
1590
+ isAtBeginning,
1591
+ isAtEnd,
1592
+ scrollPosition,
1593
+ });
1594
+ }, [isScrollable, isAtBeginning, isAtEnd, scrollPosition, onScrollStateChange, isFirstRender]);
1554
1595
  react.useEffect(() => {
1555
1596
  const element = elementRef?.current;
1556
1597
  if (!element) {
@@ -1572,7 +1613,7 @@ const useScrollDetection = (elementRef) => {
1572
1613
  element.removeEventListener("scroll", debouncedCheckScrollPosition);
1573
1614
  };
1574
1615
  }, [elementRef, checkScrollable, checkScrollPosition, debouncedCheckScrollPosition]);
1575
- return { isScrollable, isAtTop, isAtBottom, scrollPosition };
1616
+ return react.useMemo(() => ({ isScrollable, isAtBeginning, isAtEnd, scrollPosition }), [isScrollable, isAtBeginning, isAtEnd, scrollPosition]);
1576
1617
  };
1577
1618
 
1578
1619
  /**
@@ -1588,6 +1629,36 @@ const useSelfUpdatingRef = (initialState) => {
1588
1629
  return stateRef;
1589
1630
  };
1590
1631
 
1632
+ /**
1633
+ * The useStable hook ensures that a value is computed only once and remains stable across renders.
1634
+ * This is useful for expensive computations or when you need a value to never change after initial creation.
1635
+ * Perfect for generating stable random values, creating stable object references, or any one-time computation.
1636
+ *
1637
+ * @example
1638
+ * // Stable random ID that never changes
1639
+ * const stableId = useStable(() => Math.random().toString(36));
1640
+ * @example
1641
+ * // Stable configuration object
1642
+ * const config = useStable(() => ({
1643
+ * apiKey: generateApiKey(),
1644
+ * timestamp: Date.now()
1645
+ * }));
1646
+ * @example
1647
+ * // Stable random widths for skeleton loading
1648
+ * const lineWidths = useStable(() => ({
1649
+ * title: Math.floor(Math.random() * 100) + 120,
1650
+ * description: Math.floor(Math.random() * 80) + 80
1651
+ * }));
1652
+ */
1653
+ const useStable = (factory) => {
1654
+ const ref = react.useRef(null);
1655
+ // Compute value only once and store it
1656
+ if (ref.current === null) {
1657
+ ref.current = { value: factory() };
1658
+ }
1659
+ return ref.current.value;
1660
+ };
1661
+
1591
1662
  /**
1592
1663
  * A custom React hook that provides real-time information about the current viewport size.
1593
1664
  * ! Consider using `useContainerBreakpoints` instead, and only use this when you need to actually react to the viewport size, not the container size.
@@ -1757,11 +1828,9 @@ const useBreadcrumbItemsToRender = (breadcrumbItems) => {
1757
1828
  const breadCrumbItemsToJSX = breadcrumbItems.map((item, index, array) => {
1758
1829
  const isLast = index === array.length - 1;
1759
1830
  if (!isLast) {
1760
- return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(Button, { asChild: true, size: "small", variant: "ghost-neutral", children: jsxRuntime.jsx(reactRouter.Link, { to: item.to, children: item.label }) }), jsxRuntime.jsx(Icon, { className: "text-secondary-300", name: "Slash", size: "small" })] }));
1761
- }
1762
- else {
1763
- return (jsxRuntime.jsx(Text, { className: "text-nowrap", size: "small", children: item.label }));
1831
+ return (jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx(Button, { asChild: true, size: "small", variant: "ghost-neutral", children: jsxRuntime.jsx(reactRouter.Link, { to: item.to, children: item.label }) }), jsxRuntime.jsx(Icon, { className: "text-secondary-300", name: "Slash", size: "small" })] }, index));
1764
1832
  }
1833
+ return (jsxRuntime.jsx(Text, { className: "text-nowrap", size: "small", children: item.label }, index));
1765
1834
  });
1766
1835
  return breadCrumbItemsToJSX;
1767
1836
  };
@@ -2185,42 +2254,63 @@ const DetailsList = ({ details, className, density = "default" }) => {
2185
2254
  return (jsxRuntime.jsx("div", { className: cvaDetailsList({ className, density }), children: details.map((value, index, array) => (jsxRuntime.jsxs(react.Fragment, { children: [jsxRuntime.jsx("span", { className: cvaDetailsListItem({ className }), children: value }), index < array.length - 1 && (jsxRuntime.jsx("div", { className: "mx-0.5 flex items-center", children: jsxRuntime.jsx(Icon, { className: "w-4 text-neutral-300", color: "neutral", name: "Slash", size: "small" }) }))] }, index))) }));
2186
2255
  };
2187
2256
 
2257
+ /**
2258
+ * Generates a random width percentage string for skeleton loading components.
2259
+ *
2260
+ * @param {object} params - The parameter object
2261
+ * @param {number} params.min - Minimum percentage value (e.g., 30 for 30%)
2262
+ * @param {number} params.max - Maximum percentage value (e.g., 80 for 80%)
2263
+ * @returns {string} A percentage string (e.g., "65%")
2264
+ */
2265
+ const getResponsiveRandomWidthPercentage = ({ min, max }) => {
2266
+ const randomWidth = Math.floor(Math.random() * (max - min + 1)) + min;
2267
+ return `${randomWidth}%`;
2268
+ };
2269
+
2270
+ const cvaSkeletonContainer = cssClassVarianceUtilities.cvaMerge(["flex", "flex-col"]);
2188
2271
  const cvaSkeletonLine = cssClassVarianceUtilities.cvaMerge([
2189
- "rounded-md",
2190
- "h-3",
2191
- "opacity-20",
2192
- "animate-pulse",
2193
- "bg-black/50",
2194
- "bg-auto",
2272
+ "relative",
2273
+ "overflow-hidden",
2274
+ "rounded-lg",
2275
+ // Gradient background
2195
2276
  "bg-gradient-to-r",
2196
- "from-black/0",
2197
- "via-black/50",
2198
- "to-black/20",
2277
+ "from-gray-200/80",
2278
+ "via-gray-300/60",
2279
+ "to-gray-200/80",
2280
+ // Pulse animation
2281
+ "animate-pulse",
2282
+ // Shimmer overlay
2283
+ "before:absolute",
2284
+ "before:inset-0",
2285
+ "before:bg-gradient-to-r",
2286
+ "before:from-transparent",
2287
+ "before:via-white/50",
2288
+ "before:to-transparent",
2289
+ "before:opacity-0",
2290
+ "before:animate-pulse",
2291
+ // Smooth transitions for accessibility
2292
+ "transition-all",
2293
+ "duration-300",
2294
+ "ease-in-out",
2199
2295
  ]);
2200
2296
 
2201
2297
  /**
2202
2298
  * Display placeholder lines before the data gets loaded to reduce load-time frustration.
2203
2299
  */
2204
- const SkeletonLines = react.memo(({ margin = "10px 0", lines = 1, height, width = "100%", className, dataTestId, }) => {
2205
- const skeletonLines = [];
2206
- const getWidth = (index) => {
2207
- if (Array.isArray(width)) {
2208
- return width[index] ?? "100%";
2209
- }
2210
- return width;
2211
- };
2212
- const getHeight = (index) => {
2213
- if (Array.isArray(height)) {
2214
- return height[index] ?? "auto";
2215
- }
2216
- return height;
2217
- };
2218
- for (let i = 0; i < lines; i++) {
2219
- skeletonLines.push(jsxRuntime.jsx("div", { className: cvaSkeletonLine({ className }), "data-testid": dataTestId ? `${dataTestId}-${i}` : `skeleton-lines-${i}`, "data-type": "loading-skeleton-line", style: { height: getHeight(i), width: getWidth(i), margin: lines > 1 && i >= 1 ? margin : "" }, children: lines > 1 && jsxRuntime.jsx(jsxRuntime.Fragment, { children: "\u00A0" }) }, i));
2220
- }
2221
- return jsxRuntime.jsx(jsxRuntime.Fragment, { children: skeletonLines });
2300
+ const SkeletonLines = react.memo(({ lines = 1, height = "0.75rem", width = "100%", margin = 10, className, dataTestId }) => {
2301
+ const gapStyle = typeof margin === "number" ? `${margin}px` : margin;
2302
+ return (jsxRuntime.jsx("div", { "aria-label": `Loading ${lines} ${lines === 1 ? "item" : "items"}`, className: cvaSkeletonContainer({ className }), "data-testid": dataTestId, role: "status", style: { gap: gapStyle }, children: Array.from({ length: lines }, (_, index) => (jsxRuntime.jsx("div", { className: cvaSkeletonLine(), "data-testid": dataTestId ? `${dataTestId}-${index}` : `skeleton-lines-${index}`, "data-type": "loading-skeleton-line", style: {
2303
+ width: getDimension(width, index),
2304
+ height: getDimension(height, index),
2305
+ } }, index))) }));
2222
2306
  });
2223
- SkeletonLines.displayName = "SkeletonLines";
2307
+ const getDimension = (dimension, index) => {
2308
+ if (Array.isArray(dimension)) {
2309
+ const value = dimension[index] ?? dimension[0] ?? "100%";
2310
+ return typeof value === "number" ? `${value}px` : value;
2311
+ }
2312
+ return typeof dimension === "number" ? `${dimension}px` : dimension;
2313
+ };
2224
2314
 
2225
2315
  const cvaContainerStyles = cssClassVarianceUtilities.cvaMerge([
2226
2316
  "flex",
@@ -2392,6 +2482,119 @@ const Highlight = ({ className, dataTestId, children, size = "small", color = "w
2392
2482
  };
2393
2483
  Highlight.displayName = "Highlight";
2394
2484
 
2485
+ const cvaZStackContainer = cssClassVarianceUtilities.cvaMerge(["grid", "grid-cols-1", "grid-rows-1"]);
2486
+ const cvaZStackItem = cssClassVarianceUtilities.cvaMerge(["col-start-1", "col-end-1", "row-start-1", "row-end-2"]);
2487
+
2488
+ /**
2489
+ * ZStack is a component that stacks its children on the z-axis.
2490
+ * Is a good alternative to "position: absolute" that avoids some of the unfortunate side effects of absolute positioning.
2491
+ *
2492
+ * @param { ZStackProps} props - The props for the ZStack component
2493
+ * @returns {Element} ZStack component
2494
+ */
2495
+ const ZStack = ({ children, className, dataTestId }) => {
2496
+ return (jsxRuntime.jsx("div", { className: cvaZStackContainer({ className }), "data-testid": dataTestId, children: react.Children.map(children, (child, index) => {
2497
+ if (!react.isValidElement(child)) {
2498
+ return child;
2499
+ }
2500
+ return react.cloneElement(child, {
2501
+ className: cvaZStackItem({ className: child.props.className }),
2502
+ key: index,
2503
+ });
2504
+ }) }));
2505
+ };
2506
+
2507
+ const cvaHorizontalOverflowScroller = cssClassVarianceUtilities.cvaMerge([
2508
+ "flex",
2509
+ "flex-nowrap",
2510
+ "gap-1",
2511
+ "overflow-y-hidden",
2512
+ "overflow-x-scroll",
2513
+ "w-full",
2514
+ "no-scrollbar",
2515
+ ]);
2516
+ const cvaHorizontalOverflowScrollerAndIndicatorsContainer = cssClassVarianceUtilities.cvaMerge(["group", "w-full", "overflow-clip"]);
2517
+
2518
+ const cvaOverflowIndicatorContainer = cssClassVarianceUtilities.cvaMerge(["pointer-events-none", "h-full", "w-full", "isolate"]);
2519
+ const cvaOverflowIndicatorGradient = cssClassVarianceUtilities.cvaMerge(["pointer-events-none", "h-full", "w-8"], {
2520
+ variants: {
2521
+ direction: {
2522
+ left: ["bg-gradient-to-r", "from-white", "to-transparent"],
2523
+ right: ["bg-gradient-to-l", "from-white", "to-transparent"],
2524
+ },
2525
+ },
2526
+ });
2527
+ const cvaJustificationContainer = cssClassVarianceUtilities.cvaMerge(["flex", "w-full", "items-center", "pointer-events-none"], {
2528
+ variants: {
2529
+ direction: {
2530
+ left: ["justify-start"],
2531
+ right: ["justify-end"],
2532
+ },
2533
+ },
2534
+ });
2535
+ const cvaOverflowIndicatorButton = cssClassVarianceUtilities.cvaMerge([
2536
+ "shadow-md",
2537
+ "opacity-0",
2538
+ "transition-opacity",
2539
+ "duration-200",
2540
+ "pointer-events-auto",
2541
+ "starting:opacity-0",
2542
+ "group-hover:opacity-100",
2543
+ ]);
2544
+
2545
+ /**
2546
+ * Overflow indicator component that shows visual cues when content extends beyond visible area
2547
+ * Shows a scroll button on hover to navigate in the specified direction
2548
+ *
2549
+ * @param {OverflowIndicatorProps} props - The props for the component
2550
+ * @returns {ReactElement} OverflowIndicator component
2551
+ */
2552
+ const OverflowIndicator = ({ className, dataTestId, direction, onClickScroll, }) => {
2553
+ const iconName = direction === "left" ? "ChevronLeft" : "ChevronRight";
2554
+ return (jsxRuntime.jsxs(ZStack, { className: cvaOverflowIndicatorContainer({ className }), dataTestId: dataTestId, children: [jsxRuntime.jsx("div", { className: cvaJustificationContainer({ direction }), children: jsxRuntime.jsx("div", { className: cvaOverflowIndicatorGradient({ direction }), "data-testid": dataTestId ? `${dataTestId}-gradient` : undefined }) }), jsxRuntime.jsx("div", { className: cvaJustificationContainer({ direction }), children: jsxRuntime.jsx(IconButton, { circular: true, className: cvaOverflowIndicatorButton(), "data-testid": dataTestId ? `${dataTestId}-button` : undefined, icon: jsxRuntime.jsx(Icon, { name: iconName, size: "small" }), onClick: onClickScroll, size: "small", variant: "secondary" }) })] }));
2555
+ };
2556
+
2557
+ /**
2558
+ * Container for displaying components in a horizontal layout with overflow detection
2559
+ *
2560
+ * @param {HorizontalOverflowScrollerProps} props - The props for the component
2561
+ * @returns {Element} HorizontalOverflowScroller component
2562
+ */
2563
+ const HorizontalOverflowScroller = ({ className, dataTestId, children, onScrollStateChange, }) => {
2564
+ const containerRef = react.useRef(null);
2565
+ const childrenArray = react.Children.toArray(children);
2566
+ const { width: containerWidth } = useGeometry(containerRef);
2567
+ const { isScrollable, isAtBeginning, isAtEnd } = useScrollDetection(containerRef, {
2568
+ direction: "horizontal",
2569
+ onScrollStateChange: onScrollStateChange
2570
+ ? (state) => onScrollStateChange({
2571
+ isScrollable: state.isScrollable,
2572
+ isAtBeginning: state.isAtBeginning,
2573
+ isAtEnd: state.isAtEnd,
2574
+ })
2575
+ : undefined,
2576
+ });
2577
+ const handleScrollLeft = () => {
2578
+ const element = containerRef.current;
2579
+ if (!element || !containerWidth)
2580
+ return;
2581
+ element.scrollBy({
2582
+ left: -containerWidth,
2583
+ behavior: "smooth",
2584
+ });
2585
+ };
2586
+ const handleScrollRight = () => {
2587
+ const element = containerRef.current;
2588
+ if (!element || !containerWidth)
2589
+ return;
2590
+ element.scrollBy({
2591
+ left: containerWidth,
2592
+ behavior: "smooth",
2593
+ });
2594
+ };
2595
+ return (jsxRuntime.jsxs(ZStack, { className: cvaHorizontalOverflowScrollerAndIndicatorsContainer({ className }), children: [jsxRuntime.jsx("div", { className: cvaHorizontalOverflowScroller({ className }), "data-testid": dataTestId, ref: containerRef, children: childrenArray.map((child, index) => (jsxRuntime.jsx(react.Fragment, { children: child }, index))) }), isScrollable && !isAtBeginning ? (jsxRuntime.jsx(OverflowIndicator, { dataTestId: `${dataTestId}-left-indicator`, direction: "left", onClickScroll: handleScrollLeft })) : null, isScrollable && !isAtEnd ? (jsxRuntime.jsx(OverflowIndicator, { dataTestId: `${dataTestId}-right-indicator`, direction: "right", onClickScroll: handleScrollRight })) : null] }));
2596
+ };
2597
+
2395
2598
  const PADDING = 12;
2396
2599
  /**
2397
2600
  * Converts a width size value into a CSS dimension value for max constraints
@@ -2776,7 +2979,7 @@ const FloatingArrowContainer = ({ arrowRef, mode = "dark", }) => {
2776
2979
  * @param {TooltipProps} props - The props for the Tooltip component
2777
2980
  * @returns {ReactElement} Tooltip component
2778
2981
  */
2779
- const Tooltip = ({ children, dataTestId, disabled = false, className, label, placement = "auto", mode = "dark", iconProps, id, }) => {
2982
+ const Tooltip = ({ children, dataTestId, disabled = false, className, label, placement = "auto", mode = "dark", iconProps, id, style, }) => {
2780
2983
  const [isOpen, setIsOpen] = react.useState(false);
2781
2984
  const arrowRef = react.useRef(null);
2782
2985
  const { refs, floatingStyles, context } = react$1.useFloating({
@@ -2809,7 +3012,7 @@ const Tooltip = ({ children, dataTestId, disabled = false, className, label, pla
2809
3012
  }
2810
3013
  setIsOpen(false);
2811
3014
  }, [disabled]);
2812
- return (jsxRuntime.jsxs(Popover, { activation: { hover: true }, className: cvaTooltipPopover(), dataTestId: dataTestId, id: id, placement: placement === "auto" ? "bottom" : placement, children: [jsxRuntime.jsx(PopoverTrigger, { className: cvaTooltipIcon({ color: mode, className }), "data-testid": dataTestId ? `${dataTestId}-trigger` : null, onMouseDown: closeTooltip, onMouseEnter: openTooltip, onMouseLeave: closeTooltip, ref: refs.setReference, children: children === undefined ? (jsxRuntime.jsx("div", { children: jsxRuntime.jsx(Icon, { dataTestId: dataTestId ? `${dataTestId}-icon` : undefined, name: "QuestionMarkCircle", size: "small", ...iconProps }) })) : (wrappedChildren) }), isMounted ? (jsxRuntime.jsx("div", { ref: refs.setFloating, style: floatingStyles, children: jsxRuntime.jsx(PopoverContent, { children: jsxRuntime.jsxs("div", { "aria-label": typeof label === "string" ? label : undefined, className: cvaTooltipPopoverContent({ color: mode }), "data-testid": `${dataTestId}-content`, children: [jsxRuntime.jsx(Text, { dataTestId: `${dataTestId}-text`, inverted: mode === "dark", size: "small", type: typeof label === "string" ? "p" : "span", children: label }), placement !== "auto" && jsxRuntime.jsx(FloatingArrowContainer, { arrowRef: arrowRef, mode: mode })] }) }) })) : null] }));
3015
+ return (jsxRuntime.jsxs(Popover, { activation: { hover: true }, className: cvaTooltipPopover(), dataTestId: dataTestId, id: id, placement: placement === "auto" ? "bottom" : placement, children: [jsxRuntime.jsx(PopoverTrigger, { className: cvaTooltipIcon({ color: mode, className }), "data-testid": dataTestId ? `${dataTestId}-trigger` : null, onMouseDown: closeTooltip, onMouseEnter: openTooltip, onMouseLeave: closeTooltip, ref: refs.setReference, style: style, children: children === undefined ? (jsxRuntime.jsx("div", { children: jsxRuntime.jsx(Icon, { dataTestId: dataTestId ? `${dataTestId}-icon` : undefined, name: "QuestionMarkCircle", size: "small", ...iconProps }) })) : (wrappedChildren) }), isMounted ? (jsxRuntime.jsx("div", { ref: refs.setFloating, style: floatingStyles, children: jsxRuntime.jsx(PopoverContent, { children: jsxRuntime.jsxs("div", { "aria-label": typeof label === "string" ? label : undefined, className: cvaTooltipPopoverContent({ color: mode }), "data-testid": `${dataTestId}-content`, children: [jsxRuntime.jsx(Text, { dataTestId: `${dataTestId}-text`, inverted: mode === "dark", size: "small", type: typeof label === "string" ? "p" : "span", children: label }), placement !== "auto" && jsxRuntime.jsx(FloatingArrowContainer, { arrowRef: arrowRef, mode: mode })] }) }) })) : null] }));
2813
3016
  };
2814
3017
 
2815
3018
  const cvaIndicator = cssClassVarianceUtilities.cvaMerge(["flex", "items-center"]);
@@ -2946,12 +3149,12 @@ const Indicator = ({ dataTestId, icon, label, color = "unknown", withBackground
2946
3149
  return (jsxRuntime.jsx(Tooltip, { className: className, disabled: withLabel, label: label, placement: "bottom", children: jsxRuntime.jsxs("div", { "aria-label": label, className: cvaIndicator(), "data-testid": dataTestId, ...rest, children: [jsxRuntime.jsxs("div", { className: cvaIndicatorIconBackground({ color, background: withBackground ? "visible" : "hidden" }), "data-testid": dataTestId ? `${dataTestId}-background` : "indicator-background", children: [ping ? (jsxRuntime.jsx("div", { className: cvaIndicatorPing({ color }), "data-testid": dataTestId ? `${dataTestId}-ping` : "indicator-ping" })) : null, icon] }), label && withLabel ? (jsxRuntime.jsx("div", { className: cvaIndicatorLabel({ size, weight, background: withBackground ? "visible" : "hidden" }), "data-testid": dataTestId ? `${dataTestId}-label` : undefined, children: label })) : null] }) }));
2947
3150
  };
2948
3151
 
2949
- const cvaKPI = cssClassVarianceUtilities.cvaMerge(["w-full", "px-4", "py-2", "flex", "flex-col"], {
3152
+ const cvaKPI = cssClassVarianceUtilities.cvaMerge(["w-full", "flex", "flex-col"], {
2950
3153
  variants: {
2951
3154
  variant: {
2952
- small: ["px-3"],
3155
+ small: ["px-3", "py-2"],
2953
3156
  condensed: ["px-2", "py-0"],
2954
- default: [""],
3157
+ default: ["px-4", "py-2"],
2955
3158
  },
2956
3159
  },
2957
3160
  defaultVariants: {
@@ -3003,9 +3206,9 @@ const LoadingContent$1 = () => (jsxRuntime.jsx("div", { className: "flex h-11 fl
3003
3206
  * @param {KPIProps} props - The props for the KPI component
3004
3207
  * @returns {ReactElement} KPI component
3005
3208
  */
3006
- const KPI = ({ title, value, loading = false, unit, className, dataTestId, tooltipLabel, variant = "default", trend, ...rest }) => {
3209
+ const KPI = ({ title, value, loading = false, unit, className, dataTestId, tooltipLabel, variant = "default", trend, style, ...rest }) => {
3007
3210
  const isSmallVariant = variant === "small";
3008
- return (jsxRuntime.jsx(Tooltip, { dataTestId: dataTestId ? `${dataTestId}-tooltip` : undefined, disabled: tooltipLabel === undefined || tooltipLabel === "", label: tooltipLabel, placement: "bottom", children: jsxRuntime.jsx("div", { className: cvaKPI({ variant, className }), "data-testid": dataTestId ? `${dataTestId}` : undefined, ...rest, children: loading ? (jsxRuntime.jsx(LoadingContent$1, {})) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("div", { className: cvaKPIHeader(), children: jsxRuntime.jsx(Text, { className: cvaKPITitleText(), dataTestId: dataTestId ? `${dataTestId}-title` : undefined, size: isSmallVariant ? "small" : "medium", subtle: true, weight: isSmallVariant ? "normal" : "bold", children: title }) }), jsxRuntime.jsx(Text, { className: cvaKPIvalueText({ variant }), dataTestId: dataTestId ? `${dataTestId}-value` : undefined, size: isSmallVariant ? "small" : "large", type: "div", weight: isSmallVariant ? "bold" : "thick", children: jsxRuntime.jsxs("div", { className: cvaKPIValueContainer({
3211
+ return (jsxRuntime.jsx(Tooltip, { dataTestId: dataTestId ? `${dataTestId}-tooltip` : undefined, disabled: tooltipLabel === undefined || tooltipLabel === "", label: tooltipLabel, placement: "bottom", style: style, children: jsxRuntime.jsx("div", { className: cvaKPI({ variant, className }), "data-testid": dataTestId ? `${dataTestId}` : undefined, ...rest, children: loading ? (jsxRuntime.jsx(LoadingContent$1, {})) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("div", { className: cvaKPIHeader(), children: jsxRuntime.jsx(Text, { className: cvaKPITitleText(), dataTestId: dataTestId ? `${dataTestId}-title` : undefined, size: isSmallVariant ? "small" : "medium", subtle: true, weight: isSmallVariant ? "normal" : "bold", children: title }) }), jsxRuntime.jsx(Text, { className: cvaKPIvalueText({ variant }), dataTestId: dataTestId ? `${dataTestId}-value` : undefined, size: isSmallVariant ? "small" : "large", type: "div", weight: isSmallVariant ? "bold" : "thick", children: jsxRuntime.jsxs("div", { className: cvaKPIValueContainer({
3009
3212
  isDefaultAndHasTrendValue: Boolean(trend !== undefined && trend.value !== undefined && !isSmallVariant),
3010
3213
  className,
3011
3214
  }), children: [jsxRuntime.jsxs("span", { className: cvaKPIvalueText({ variant }), children: [value, " ", unit] }), jsxRuntime.jsx(TrendIndicator, { isSmallVariant: isSmallVariant, trend: trend, unit: unit })] }) })] })) }) }));
@@ -3077,25 +3280,66 @@ const cvaCardBodyDensityContainer = cssClassVarianceUtilities.cvaMerge(["grid",
3077
3280
  },
3078
3281
  });
3079
3282
 
3080
- const cvaListContainer = cssClassVarianceUtilities.cvaMerge(["h-full"], {
3283
+ const cvaListItem$1 = cssClassVarianceUtilities.cvaMerge(["py-3", "px-4", "min-h-14", "w-full", "flex", "justify-between", "items-center"]);
3284
+ const cvaMainInformationClass = cssClassVarianceUtilities.cvaMerge(["grid", "items-center", "text-sm", "gap-2"], {
3285
+ variants: {
3286
+ hasThumbnail: {
3287
+ true: "grid-cols-min-fr",
3288
+ false: "grid-cols-1",
3289
+ },
3290
+ },
3291
+ });
3292
+ const cvaThumbnailContainer = cssClassVarianceUtilities.cvaMerge([
3293
+ "flex",
3294
+ "h-8",
3295
+ "w-8",
3296
+ "items-center",
3297
+ "justify-center",
3298
+ "overflow-hidden",
3299
+ "rounded-md",
3300
+ ]);
3301
+
3302
+ const DEFAULT_SKELETON_LIST_ITEM_PROPS = {
3303
+ hasThumbnail: true,
3304
+ thumbnailShape: "circle",
3305
+ hasDescription: true,
3306
+ hasMeta: false,
3307
+ hasDetails: false,
3308
+ };
3309
+ /**
3310
+ * Skeleton loading indicator that mimics the ListItem component structure.
3311
+ * Uses the same layout, spacing, and visual hierarchy as ListItem.
3312
+ */
3313
+ const ListItemSkeleton = ({ hasThumbnail = DEFAULT_SKELETON_LIST_ITEM_PROPS.hasThumbnail, thumbnailShape = "circle", hasDescription = DEFAULT_SKELETON_LIST_ITEM_PROPS.hasDescription, hasMeta = DEFAULT_SKELETON_LIST_ITEM_PROPS.hasMeta, hasDetails = DEFAULT_SKELETON_LIST_ITEM_PROPS.hasDetails, }) => {
3314
+ // Generate stable random widths once and never change them
3315
+ const lineWidths = useStable(() => {
3316
+ return {
3317
+ title: getResponsiveRandomWidthPercentage({ min: 60, max: 85 }),
3318
+ description: getResponsiveRandomWidthPercentage({ min: 45, max: 70 }),
3319
+ meta: getResponsiveRandomWidthPercentage({ min: 30, max: 55 }),
3320
+ details: getResponsiveRandomWidthPercentage({ min: 25, max: 45 }),
3321
+ };
3322
+ });
3323
+ return (jsxRuntime.jsxs("div", { className: cvaListItem$1({ className: "w-full" }), children: [jsxRuntime.jsxs("div", { className: cvaMainInformationClass({ hasThumbnail, className: "w-full" }), children: [hasThumbnail ? (jsxRuntime.jsx("div", { className: cvaThumbnailContainer({ className: "bg-gray-200" }), children: jsxRuntime.jsx("div", { className: tailwindMerge.twMerge("bg-gray-300", thumbnailShape === "circle" ? "rounded-full" : "rounded"), style: { width: 20, height: 20 } }) })) : null, jsxRuntime.jsxs("div", { className: "grid-rows-min-fr grid w-full items-center gap-1 text-sm", children: [jsxRuntime.jsx(SkeletonLines, { height: "0.875em", lines: 1, width: lineWidths.title }), hasDescription ? jsxRuntime.jsx(SkeletonLines, { height: "0.75em", lines: 1, width: lineWidths.description }) : null, hasMeta ? jsxRuntime.jsx(SkeletonLines, { height: "0.75em", lines: 1, width: lineWidths.meta }) : null] })] }), hasDetails ? (jsxRuntime.jsx("div", { className: "pl-2 text-sm", children: jsxRuntime.jsx(SkeletonLines, { height: "0.875em", lines: 1, width: lineWidths.details }) })) : null] }));
3324
+ };
3325
+
3326
+ const cvaListContainer = cssClassVarianceUtilities.cvaMerge(["overflow-auto", "h-full"], {
3081
3327
  variants: {
3082
- parentControlledScrollable: {
3083
- true: [""],
3084
- false: ["overflow-auto"],
3328
+ withTopSeparator: {
3329
+ true: ["border-t", "border-neutral-200", "transition-colors duration-200 ease-in"],
3330
+ false: ["border-t", "border-transparent", "transition-colors duration-200 ease-in"],
3085
3331
  },
3086
3332
  },
3087
3333
  defaultVariants: {
3088
- parentControlledScrollable: false,
3334
+ withTopSeparator: false,
3089
3335
  },
3090
3336
  });
3091
3337
  const cvaList = cssClassVarianceUtilities.cvaMerge(["relative"]);
3092
- const cvaListItem$1 = cssClassVarianceUtilities.cvaMerge(["absolute", "top-0", "left-0", "w-full"], {
3338
+ const cvaListItem = cssClassVarianceUtilities.cvaMerge(["absolute", "top-0", "left-0", "w-full"], {
3093
3339
  variants: {
3094
3340
  separator: {
3095
- alternating: ["even:bg-slate-100"],
3096
- line: ["[&:not(:last-child)]:border-b", "border-gray-200"],
3341
+ line: ["[&:not(:last-child)]:border-b", "border-neutral-200"],
3097
3342
  none: "",
3098
- space: "[&:not(:last-child)]:pb-0.5",
3099
3343
  },
3100
3344
  },
3101
3345
  defaultVariants: {
@@ -3104,62 +3348,129 @@ const cvaListItem$1 = cssClassVarianceUtilities.cvaMerge(["absolute", "top-0", "
3104
3348
  });
3105
3349
 
3106
3350
  /**
3107
- * Render a performant virtualized list of items. Optionally with infinite scrolling.
3108
3351
  *
3109
- * @property {number} count - The total number of items in the list.
3110
- * @property {number} [rowHeight="40"] - The estimated height of each row in the list.
3111
- * @property {RelayPagination | undefined} pagination - Pagination configuration for the list.
3112
- * @property {separator} [separator="line"] - The separator style between items in the list.
3113
- * @property {(index: number) =>ReactElement} children - A function that takes an index and returns the JSX element to be rendered at said index.
3114
- * @property {loadingIndicator} [loadingIndicator="spinner"] - The type of loading indicator in the list.
3115
- * @property {skeletonLinesHeight} [skeletonLinesHeight="2rem"] - The height of the skeleton lines.
3116
3352
  */
3117
- const List = ({ count, rowHeight = 40, pagination, children, className, dataTestId, separator = "none", loadingIndicator = "spinner", skeletonLinesHeight = rowHeight + "px", onRowClick, scrollRef, }) => {
3118
- const containerRef = react.useRef(null);
3119
- const listRef = react.useRef(null);
3120
- const [scrollParent, setScrollParent] = react.useState(null);
3121
- const [parentControlledScrollable, setParentControlledScrollable] = react.useState(false);
3122
- react.useEffect(() => {
3123
- if (scrollRef?.current) {
3124
- setParentControlledScrollable(true);
3125
- setScrollParent(scrollRef.current);
3353
+ const ListLoadingIndicator = ({ type, hasThumbnail, thumbnailShape, hasDescription, component, hasMeta, hasDetails, }) => {
3354
+ switch (type) {
3355
+ case "none":
3356
+ return null;
3357
+ case "spinner":
3358
+ return jsxRuntime.jsx(Spinner, { centering: "horizontally", containerClassName: "p-4" });
3359
+ case "custom":
3360
+ return component;
3361
+ case "skeleton":
3362
+ return (jsxRuntime.jsx(ListItemSkeleton, { hasDescription: hasDescription, hasDetails: hasDetails, hasMeta: hasMeta, hasThumbnail: hasThumbnail, thumbnailShape: thumbnailShape }));
3363
+ default: {
3364
+ throw new Error(`${type} is not known`);
3126
3365
  }
3127
- else {
3128
- setParentControlledScrollable(false);
3129
- setScrollParent(containerRef.current);
3366
+ }
3367
+ };
3368
+
3369
+ const DEFAULT_ROW_HEIGHT = 61; // 61px is the height of the ListItem component as of 2025-09-26
3370
+ /**
3371
+ * A performant virtualized list component with infinite scrolling support.
3372
+ *
3373
+ * ⚠️ **Important**: Requires a container with defined height to work properly.
3374
+ *
3375
+ * Features:
3376
+ * - Virtualized rendering using TanStack Virtual for performance with large datasets
3377
+ * - Automatic infinite scroll loading when approaching the end of the list
3378
+ * - Optional header support (automatically managed, scrolls with content)
3379
+ * - Built-in pagination with relay-style cursor support
3380
+ * - Configurable loading indicators (skeleton, spinner, or custom)
3381
+ * - Scroll state detection and callbacks
3382
+ * - Variable-height item support via `estimateItemSize`
3383
+ *
3384
+ * The component automatically loads more data when:
3385
+ * - User scrolls to the last visible item
3386
+ * - Content height is insufficient to fill the container
3387
+ */
3388
+ const List = ({ count, pagination, children, className, dataTestId, separator = "line", loadingIndicator = { type: "skeleton", ...DEFAULT_SKELETON_LIST_ITEM_PROPS }, onRowClick, onScrollStateChange, topSeparatorOnScroll = false, estimateItemSize, header, getItem, }) => {
3389
+ const parentRef = react.useRef(null);
3390
+ // Calculate the actual count including header
3391
+ const actualCount = count + (header ? 1 : 0);
3392
+ // Helper function to get item for a given data index
3393
+ const getItemAtIndex = react.useCallback((dataIndex) => {
3394
+ return getItem(dataIndex);
3395
+ }, [getItem]);
3396
+ // Calculate how many loading rows we need
3397
+ const getLoadingRowsCount = react.useCallback(() => {
3398
+ const { type: loadingIndicatorType } = loadingIndicator;
3399
+ if (pagination?.isLoading === false)
3400
+ return 0;
3401
+ switch (loadingIndicatorType) {
3402
+ case "none":
3403
+ return 0;
3404
+ case "spinner":
3405
+ return 1;
3406
+ case "custom":
3407
+ case "skeleton": {
3408
+ const isInitialLoading = !pagination?.pageInfo;
3409
+ const initialCount = loadingIndicator.initialLoadingCount ?? 10;
3410
+ const scrollCount = loadingIndicator.scrollLoadingCount ?? 3;
3411
+ return isInitialLoading ? initialCount : scrollCount;
3412
+ }
3413
+ default: {
3414
+ throw new Error(`${loadingIndicatorType} is not known`);
3415
+ }
3130
3416
  }
3131
- }, [scrollRef]);
3132
- const infiniteScrollProps = react.useMemo(() => {
3133
- return {
3134
- pagination: pagination || reactTablePagination.noPagination,
3135
- containerRef: { current: scrollParent },
3136
- rowSize: pagination !== undefined &&
3137
- pagination.pageInfo !== undefined &&
3138
- pagination.pageInfo.hasNextPage === true &&
3139
- pagination.isLoading === true
3140
- ? count + 1
3141
- : count,
3142
- rowHeight,
3143
- };
3144
- }, [pagination, scrollParent, count, rowHeight]);
3145
- const { fetchMoreOnBottomReached, getVirtualItems, getTotalSize, measureElement } = reactTablePagination.useInfiniteScroll(infiniteScrollProps);
3146
- react.useEffect(() => {
3147
- if (scrollParent) {
3148
- const handleScroll = () => {
3149
- fetchMoreOnBottomReached(scrollParent);
3150
- };
3151
- scrollParent.addEventListener("scroll", handleScroll);
3152
- return () => {
3153
- scrollParent.removeEventListener("scroll", handleScroll);
3154
- };
3417
+ }, [loadingIndicator, pagination]);
3418
+ const estimateSize = react.useCallback((index) => {
3419
+ // Check if this is a loading row
3420
+ if (index >= actualCount) {
3421
+ const loaderIndex = index - actualCount;
3422
+ const shouldShowLoader = pagination?.isLoading === true && loaderIndex < getLoadingRowsCount();
3423
+ // Empty loader rows should be estimated at 0 height to prevent blank space
3424
+ return shouldShowLoader ? DEFAULT_ROW_HEIGHT : 0;
3155
3425
  }
3156
- return undefined;
3157
- }, [scrollParent, fetchMoreOnBottomReached]);
3158
- return (jsxRuntime.jsx("div", { className: cvaListContainer({ parentControlledScrollable, className }), "data-testid": dataTestId, ref: containerRef, children: jsxRuntime.jsx("ul", { className: cvaList(), ref: listRef, style: { height: `${getTotalSize()}px`, outline: "none" }, children: getVirtualItems().map(virtualRow => {
3159
- const isLoaderRow = virtualRow.index > count - 1;
3160
- return (jsxRuntime.jsx("li", { className: cvaListItem$1({ separator }), "data-index": virtualRow.index, onClick: onRowClick !== undefined ? () => onRowClick(virtualRow.index) : undefined, ref: measureElement, style: {
3161
- transform: `translateY(${virtualRow.start}px)`,
3162
- }, tabIndex: -1, children: isLoaderRow ? (pagination?.isLoading === true ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [loadingIndicator === "spinner" && jsxRuntime.jsx(Spinner, { centering: "horizontally", containerClassName: "p-4" }), loadingIndicator === "skeletonLines" && (jsxRuntime.jsx(SkeletonLines, { height: skeletonLinesHeight, lines: 3, width: "full" }))] })) : null) : (children(virtualRow.index)) }, virtualRow.key));
3426
+ // For data rows (including header), use custom estimator if provided
3427
+ return estimateItemSize ? estimateItemSize(index) : DEFAULT_ROW_HEIGHT;
3428
+ }, [estimateItemSize, actualCount, pagination?.isLoading, getLoadingRowsCount]);
3429
+ const { getVirtualItems, getTotalSize, measureElement, scrollOffset } = reactTablePagination.useInfiniteScroll({
3430
+ pagination: pagination || reactTablePagination.noPagination,
3431
+ containerRef: parentRef,
3432
+ count: actualCount + (pagination?.isLoading === true ? getLoadingRowsCount() : 0),
3433
+ estimateSize,
3434
+ onChange: virtualizer => onScrollStateChange?.(virtualizer.scrollOffset ?? 0, virtualizer.isScrolling),
3435
+ });
3436
+ const isAtTop = scrollOffset <= 0;
3437
+ return (jsxRuntime.jsx("div", { className: cvaListContainer({
3438
+ withTopSeparator: topSeparatorOnScroll && !isAtTop,
3439
+ className,
3440
+ }), "data-testid": dataTestId, ref: parentRef, children: jsxRuntime.jsx("ul", { className: cvaList(), style: { height: `${getTotalSize()}px` }, children: getVirtualItems().map(virtualRow => {
3441
+ const isLoaderRow = virtualRow.index >= actualCount;
3442
+ const isHeaderRow = Boolean(header) && virtualRow.index === 0;
3443
+ // Calculate data index: if header exists, subtract 1 from index for data items
3444
+ const dataIndex = Boolean(header) && !isHeaderRow ? virtualRow.index - 1 : virtualRow.index;
3445
+ // Calculate which loading indicator this is (for multiple loading indicators)
3446
+ const loaderIndex = isLoaderRow ? virtualRow.index - actualCount : 0;
3447
+ // Props required by the virtualizer for proper positioning and behavior
3448
+ const listItemProps = {
3449
+ className: cvaListItem({ separator }), // List styling (separators, spacing)
3450
+ "data-index": isLoaderRow || isHeaderRow ? virtualRow.index : dataIndex, // For accessibility and debugging
3451
+ onClick: onRowClick && !isLoaderRow && !isHeaderRow
3452
+ ? () => {
3453
+ const clickedItem = getItemAtIndex(dataIndex);
3454
+ onRowClick(clickedItem, dataIndex);
3455
+ }
3456
+ : undefined, // Row-level click handling (skip header)
3457
+ ref: measureElement, // Required for virtualizer to measure item dimensions
3458
+ style: {
3459
+ transform: `translateY(${virtualRow.start}px)`, // Critical: positions item in virtual scroll
3460
+ },
3461
+ tabIndex: -1, // Keyboard navigation support
3462
+ };
3463
+ // Handle loading rows
3464
+ if (isLoaderRow) {
3465
+ return (jsxRuntime.jsx("li", { ...listItemProps, children: pagination?.isLoading === true && loaderIndex < getLoadingRowsCount() ? (jsxRuntime.jsx(ListLoadingIndicator, { ...loadingIndicator })) : null }, virtualRow.index));
3466
+ }
3467
+ // Handle header row
3468
+ if (isHeaderRow && header) {
3469
+ return (jsxRuntime.jsx("li", { ...listItemProps, children: header }, "header"));
3470
+ }
3471
+ // For regular children, call the children function with virtualization props and item data
3472
+ const item = getItemAtIndex(dataIndex);
3473
+ return children(listItemProps, item, dataIndex);
3163
3474
  }) }) }));
3164
3475
  };
3165
3476
 
@@ -3224,35 +3535,16 @@ const cvaInteractableItem = cssClassVarianceUtilities.cvaMerge("", {
3224
3535
  },
3225
3536
  });
3226
3537
 
3227
- const cvaListItem = cssClassVarianceUtilities.cvaMerge(["py-3", "px-4", "min-h-14", "w-full", "flex", "justify-between", "items-center"]);
3228
- const cvaMainInformationClass = cssClassVarianceUtilities.cvaMerge(["grid", "items-center", "text-sm", "gap-2"], {
3229
- variants: {
3230
- hasThumbnail: {
3231
- true: "grid-cols-min-fr",
3232
- false: "grid-cols-1",
3233
- },
3234
- },
3235
- });
3236
- const cvaThumbnailContainer = cssClassVarianceUtilities.cvaMerge([
3237
- "flex",
3238
- "h-8",
3239
- "w-8",
3240
- "items-center",
3241
- "justify-center",
3242
- "overflow-hidden",
3243
- "rounded-md",
3244
- ]);
3245
-
3246
3538
  /**
3247
3539
  * The ListItem is designed to present a concise set of items for quick scanning and navigation. It supports multiple content types and actions, and its flexible layout allows for customization based on the type of data being shown - assets, events, users, etc.
3248
3540
  *
3249
3541
  * @param { ListItemProps} props - The props for the ListItem component
3250
3542
  * @returns {Element} ListItem component
3251
3543
  */
3252
- const ListItem = ({ className, dataTestId, onClick, details, title, description, meta, thumbnail, thumbnailColor = "info-600", thumbnailBackground = "info-100", }) => {
3253
- const baseClass = cvaListItem({ className });
3544
+ const ListItem = ({ className, dataTestId, onClick, details, title, description, meta, thumbnail, thumbnailColor = "info-600", thumbnailBackground = "info-100", ...rest }) => {
3545
+ const baseClass = cvaListItem$1({ className });
3254
3546
  const interactableItemClass = onClick ? tailwindMerge.twMerge(baseClass, cvaInteractableItem({ cursor: "pointer" })) : baseClass;
3255
- return (jsxRuntime.jsxs("div", { className: interactableItemClass, "data-testid": dataTestId, onClick: onClick, children: [jsxRuntime.jsxs("div", { className: cvaMainInformationClass({ hasThumbnail: !!thumbnail }), children: [thumbnail ? (jsxRuntime.jsx("div", { className: cvaThumbnailContainer({
3547
+ return (jsxRuntime.jsxs("li", { className: interactableItemClass, "data-testid": dataTestId, onClick: onClick, ...rest, children: [jsxRuntime.jsxs("div", { className: cvaMainInformationClass({ hasThumbnail: !!thumbnail }), children: [thumbnail ? (jsxRuntime.jsx("div", { className: cvaThumbnailContainer({
3256
3548
  className: `text-${thumbnailColor} bg-${thumbnailBackground}`,
3257
3549
  }), children: thumbnail })) : null, jsxRuntime.jsxs("div", { className: "grid-rows-min-fr grid items-center text-sm", children: [jsxRuntime.jsx("div", { className: "gap-responsive-space-sm flex w-full min-w-0 items-center text-sm", children: typeof title === "string" ? (jsxRuntime.jsx(Text, { className: "truncate", dataTestId: dataTestId ? `${dataTestId}-title` : undefined, weight: "bold", children: title })) : (react.cloneElement(title, {
3258
3550
  className: tailwindMerge.twMerge(title.props.className, "neutral-900 text-sm font-medium truncate"),
@@ -3260,7 +3552,7 @@ const ListItem = ({ className, dataTestId, onClick, details, title, description,
3260
3552
  })) }), description !== undefined && description !== "" ? (jsxRuntime.jsx("div", { className: "gap-responsive-space-sm flex w-full min-w-0 items-center", children: typeof description === "string" ? (jsxRuntime.jsx(Text, { className: "truncate text-xs text-neutral-500", dataTestId: dataTestId ? `${dataTestId}-description` : undefined, weight: "bold", children: description })) : (react.cloneElement(description, {
3261
3553
  className: tailwindMerge.twMerge(description.props.className, "text-neutral-500 text-xs font-medium truncate"),
3262
3554
  dataTestId: !description.props.dataTestId && dataTestId ? `${dataTestId}-description` : undefined,
3263
- })) })) : null, meta ? (jsxRuntime.jsx("div", { className: "gap-responsive-space-sm flex w-full min-w-0 items-center pt-0.5", children: jsxRuntime.jsx(Text, { className: "truncate text-xs text-neutral-400", dataTestId: dataTestId ? `${dataTestId}-meta` : undefined, weight: "bold", children: meta }) })) : null] })] }), jsxRuntime.jsxs("div", { className: "flex items-center gap-0.5 pl-2", children: [details, onClick ? jsxRuntime.jsx(Icon, { color: "neutral", name: "ChevronRight", size: "medium" }) : null] })] }));
3555
+ })) })) : null, meta ? (jsxRuntime.jsx("div", { className: "gap-responsive-space-sm flex w-full min-w-0 items-center pt-0.5", children: jsxRuntime.jsx(Text, { className: "truncate text-xs text-neutral-400", dataTestId: dataTestId ? `${dataTestId}-meta` : undefined, weight: "bold", children: meta }) })) : null] })] }), jsxRuntime.jsxs("div", { className: "flex items-center gap-0.5 text-nowrap pl-2", children: [details, onClick ? jsxRuntime.jsx(Icon, { color: "neutral", name: "ChevronRight", size: "medium" }) : null] })] }));
3264
3556
  };
3265
3557
 
3266
3558
  const cvaMenuList = cssClassVarianceUtilities.cvaMerge([
@@ -4401,28 +4693,6 @@ const ValueBar = ({ value, min = 0, max = 100, unit, size = "small", levelColors
4401
4693
  return (jsxRuntime.jsxs("span", { className: "relative flex items-center gap-2", "data-testid": dataTestId, children: [jsxRuntime.jsx("progress", { "aria-label": valueText, className: cvaValueBar({ className, size }), max: 100, style: { color: barFillColor }, value: score * 100 }), showValue && (size === "small" || size === "large") ? (jsxRuntime.jsx(Text, { className: cvaValueBarText({ size }), dataTestId: dataTestId ? `${dataTestId}-value` : undefined, children: jsxRuntime.jsx("span", { style: valueColor ? { color: valueColor } : undefined, children: valueText }) })) : null] }));
4402
4694
  };
4403
4695
 
4404
- const cvaZStackContainer = cssClassVarianceUtilities.cvaMerge(["grid", "grid-cols-1", "grid-rows-1"]);
4405
- const cvaZStackItem = cssClassVarianceUtilities.cvaMerge(["col-start-1", "col-end-1", "row-start-1", "row-end-2"]);
4406
-
4407
- /**
4408
- * ZStack is a component that stacks its children on the z-axis.
4409
- * Is a good alternative to "position: absolute" that avoids some of the unfortunate side effects of absolute positioning.
4410
- *
4411
- * @param { ZStackProps} props - The props for the ZStack component
4412
- * @returns {Element} ZStack component
4413
- */
4414
- const ZStack = ({ children, className, dataTestId }) => {
4415
- return (jsxRuntime.jsx("div", { className: cvaZStackContainer({ className }), "data-testid": dataTestId, children: react.Children.map(children, (child, index) => {
4416
- if (!react.isValidElement(child)) {
4417
- return child;
4418
- }
4419
- return react.cloneElement(child, {
4420
- className: cvaZStackItem({ className: child.props.className }),
4421
- key: index,
4422
- });
4423
- }) }));
4424
- };
4425
-
4426
4696
  const cvaClickable = cssClassVarianceUtilities.cvaMerge([
4427
4697
  "shadow-lg",
4428
4698
  "rounded-lg",
@@ -4466,6 +4736,7 @@ exports.EmptyValue = EmptyValue;
4466
4736
  exports.ExternalLink = ExternalLink;
4467
4737
  exports.Heading = Heading;
4468
4738
  exports.Highlight = Highlight;
4739
+ exports.HorizontalOverflowScroller = HorizontalOverflowScroller;
4469
4740
  exports.Icon = Icon;
4470
4741
  exports.IconButton = IconButton;
4471
4742
  exports.Indicator = Indicator;
@@ -4526,7 +4797,7 @@ exports.cvaIndicatorPing = cvaIndicatorPing;
4526
4797
  exports.cvaInteractableItem = cvaInteractableItem;
4527
4798
  exports.cvaList = cvaList;
4528
4799
  exports.cvaListContainer = cvaListContainer;
4529
- exports.cvaListItem = cvaListItem$1;
4800
+ exports.cvaListItem = cvaListItem;
4530
4801
  exports.cvaMenuItem = cvaMenuItem;
4531
4802
  exports.cvaMenuItemLabel = cvaMenuItemLabel;
4532
4803
  exports.cvaMenuItemPrefix = cvaMenuItemPrefix;
@@ -4544,6 +4815,7 @@ exports.cvaZStackContainer = cvaZStackContainer;
4544
4815
  exports.cvaZStackItem = cvaZStackItem;
4545
4816
  exports.docs = docs;
4546
4817
  exports.getDevicePixelRatio = getDevicePixelRatio;
4818
+ exports.getResponsiveRandomWidthPercentage = getResponsiveRandomWidthPercentage;
4547
4819
  exports.getValueBarColorByValue = getValueBarColorByValue;
4548
4820
  exports.iconColorNames = iconColorNames;
4549
4821
  exports.iconPalette = iconPalette;
@@ -4566,6 +4838,7 @@ exports.usePrompt = usePrompt;
4566
4838
  exports.useResize = useResize;
4567
4839
  exports.useScrollDetection = useScrollDetection;
4568
4840
  exports.useSelfUpdatingRef = useSelfUpdatingRef;
4841
+ exports.useStable = useStable;
4569
4842
  exports.useTimeout = useTimeout;
4570
4843
  exports.useViewportBreakpoints = useViewportBreakpoints;
4571
4844
  exports.useWatch = useWatch;