@trackunit/react-components 1.9.23 → 1.9.26

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
@@ -3424,7 +3424,31 @@ const cvaCardBodyDensityContainer = cssClassVarianceUtilities.cvaMerge(["grid",
3424
3424
  },
3425
3425
  });
3426
3426
 
3427
- const cvaListItem$1 = cssClassVarianceUtilities.cvaMerge(["py-3", "px-4", "min-h-14", "w-full", "flex", "justify-between", "items-center"]);
3427
+ const cvaListContainer = cssClassVarianceUtilities.cvaMerge(["overflow-y-auto", "overflow-x-hidden", "h-full"], {
3428
+ variants: {
3429
+ withTopSeparator: {
3430
+ true: ["border-t", "border-neutral-200", "transition-colors duration-200 ease-in"],
3431
+ false: ["border-t", "border-transparent", "transition-colors duration-200 ease-in"],
3432
+ },
3433
+ },
3434
+ defaultVariants: {
3435
+ withTopSeparator: false,
3436
+ },
3437
+ });
3438
+ const cvaList = cssClassVarianceUtilities.cvaMerge(["relative"]);
3439
+ const cvaListItem$1 = cssClassVarianceUtilities.cvaMerge(["absolute", "top-0", "left-0", "w-full"], {
3440
+ variants: {
3441
+ separator: {
3442
+ line: ["border-b", "border-neutral-200"],
3443
+ none: "",
3444
+ },
3445
+ },
3446
+ defaultVariants: {
3447
+ separator: "none",
3448
+ },
3449
+ });
3450
+
3451
+ const cvaListItem = cssClassVarianceUtilities.cvaMerge(["py-3", "px-4", "min-h-14", "w-full", "flex", "justify-between", "items-center"]);
3428
3452
  const cvaMainInformationClass = cssClassVarianceUtilities.cvaMerge(["grid", "items-center", "text-sm", "gap-2"], {
3429
3453
  variants: {
3430
3454
  hasThumbnail: {
@@ -3464,33 +3488,9 @@ const ListItemSkeleton = ({ hasThumbnail = DEFAULT_SKELETON_LIST_ITEM_PROPS.hasT
3464
3488
  details: getResponsiveRandomWidthPercentage({ min: 25, max: 45 }),
3465
3489
  };
3466
3490
  });
3467
- 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] }));
3491
+ return (jsxRuntime.jsxs("div", { className: cvaListItem({ 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] }));
3468
3492
  };
3469
3493
 
3470
- const cvaListContainer = cssClassVarianceUtilities.cvaMerge(["overflow-y-auto", "overflow-x-hidden", "h-full"], {
3471
- variants: {
3472
- withTopSeparator: {
3473
- true: ["border-t", "border-neutral-200", "transition-colors duration-200 ease-in"],
3474
- false: ["border-t", "border-transparent", "transition-colors duration-200 ease-in"],
3475
- },
3476
- },
3477
- defaultVariants: {
3478
- withTopSeparator: false,
3479
- },
3480
- });
3481
- const cvaList = cssClassVarianceUtilities.cvaMerge(["relative"]);
3482
- const cvaListItem = cssClassVarianceUtilities.cvaMerge(["absolute", "top-0", "left-0", "w-full"], {
3483
- variants: {
3484
- separator: {
3485
- line: ["[&:not(:last-child)]:border-b", "border-neutral-200"],
3486
- none: "",
3487
- },
3488
- },
3489
- defaultVariants: {
3490
- separator: "none",
3491
- },
3492
- });
3493
-
3494
3494
  /**
3495
3495
  *
3496
3496
  */
@@ -3575,9 +3575,9 @@ const ListLoadingIndicator = ({ type, hasThumbnail, thumbnailShape, hasDescripti
3575
3575
  * );
3576
3576
  * ```
3577
3577
  */
3578
- const List = ({ children, className, dataTestId, topSeparatorOnScroll = false, separator = "line",
3578
+ const List = ({ children, className, dataTestId,
3579
3579
  // UseListResult properties
3580
- containerRef, listRef, rows, getListItemProps, header, loadingIndicator, shouldShowLoaderAtIndex, count, isScrolling, scrollOffset,
3580
+ containerRef, listRef, rows, getListItemProps, header, loadingIndicator, shouldShowLoaderAtIndex, count, isScrolling, scrollOffset, separator, topSeparatorOnScroll,
3581
3581
  // Unused but part of UseListResult interface
3582
3582
  getTotalSize: _getTotalSize, getVirtualItems: _getVirtualItems, scrollToOffset: _scrollToOffset, scrollToIndex: _scrollToIndex, measure: _measure, }) => {
3583
3583
  return (jsxRuntime.jsx("div", { className: cvaListContainer({
@@ -3586,22 +3586,18 @@ getTotalSize: _getTotalSize, getVirtualItems: _getVirtualItems, scrollToOffset:
3586
3586
  }), "data-is-scrolling": isScrolling, "data-testid": dataTestId, ref: containerRef, children: jsxRuntime.jsx("ul", { className: cvaList(), ref: listRef, children: rows.map(row => {
3587
3587
  // Generate list item props with separator styling
3588
3588
  const listItemProps = getListItemProps(row, {
3589
- className: cvaListItem({ separator }),
3589
+ className: cvaListItem$1({ separator }),
3590
3590
  });
3591
3591
  const key = row.virtualRow.key;
3592
3592
  // Render loading row
3593
3593
  if (row.type === "loader") {
3594
- const loadingConfig = loadingIndicator ?? {
3595
- type: "skeleton",
3596
- ...DEFAULT_SKELETON_LIST_ITEM_PROPS,
3597
- };
3598
3594
  // Use the total data count from useList, not visible rows (which are virtualized)
3599
3595
  const hasHeaderRow = !!header;
3600
3596
  const dataStartIndex = hasHeaderRow ? 1 : 0;
3601
3597
  const totalDataRows = dataStartIndex + count;
3602
3598
  const loaderIndex = row.virtualRow.index - totalDataRows;
3603
3599
  const shouldShowLoader = shouldShowLoaderAtIndex(loaderIndex);
3604
- return (jsxRuntime.jsx("li", { ...listItemProps, children: shouldShowLoader ? jsxRuntime.jsx(ListLoadingIndicator, { ...loadingConfig }) : null }, key));
3600
+ return (jsxRuntime.jsx("li", { ...listItemProps, children: shouldShowLoader ? jsxRuntime.jsx(ListLoadingIndicator, { ...loadingIndicator }) : null }, key));
3605
3601
  }
3606
3602
  // Render header row
3607
3603
  if (row.type === "header") {
@@ -3649,10 +3645,12 @@ const DEFAULT_LOADING_INDICATOR_CONFIG = {
3649
3645
  * );
3650
3646
  * ```
3651
3647
  */
3652
- const useList = ({ count, pagination, header, getItem, loadingIndicator = DEFAULT_LOADING_INDICATOR_CONFIG, onRowClick, onChange, estimateItemSize, estimateHeaderSize, overscan, }) => {
3648
+ const useList = ({ count, pagination, header, getItem, loadingIndicator = DEFAULT_LOADING_INDICATOR_CONFIG, onRowClick, onChange, estimateItemSize, estimateHeaderSize, overscan, separator = "line", topSeparatorOnScroll = false, }) => {
3653
3649
  const containerRef = react.useRef(null);
3654
3650
  const listRef = react.useRef(null);
3655
3651
  const rowRefsMap = react.useRef(new Map());
3652
+ // Resolve loading indicator once to avoid unnecessary re-renders
3653
+ const resolvedLoadingIndicator = react.useMemo(() => getResolvedLoadingIndicator(loadingIndicator), [loadingIndicator]);
3656
3654
  // Calculate total count including header
3657
3655
  const hasHeader = Boolean(header);
3658
3656
  const dataStartIndex = hasHeader ? 1 : 0;
@@ -3662,7 +3660,7 @@ const useList = ({ count, pagination, header, getItem, loadingIndicator = DEFAUL
3662
3660
  const getLoadingRowsCount = react.useCallback(() => {
3663
3661
  if (pagination?.isLoading === false)
3664
3662
  return 0;
3665
- const { type: loadingIndicatorType } = loadingIndicator;
3663
+ const { type: loadingIndicatorType } = resolvedLoadingIndicator;
3666
3664
  switch (loadingIndicatorType) {
3667
3665
  case "none":
3668
3666
  return 0;
@@ -3671,20 +3669,20 @@ const useList = ({ count, pagination, header, getItem, loadingIndicator = DEFAUL
3671
3669
  case "custom":
3672
3670
  case "skeleton": {
3673
3671
  const isInitialLoading = !pagination?.pageInfo;
3674
- const initialCount = loadingIndicator.initialLoadingCount ?? 10;
3675
- const scrollCount = loadingIndicator.scrollLoadingCount ?? 3;
3672
+ const initialCount = resolvedLoadingIndicator.initialLoadingCount ?? 10;
3673
+ const scrollCount = resolvedLoadingIndicator.scrollLoadingCount ?? 3;
3676
3674
  return isInitialLoading ? initialCount : scrollCount;
3677
3675
  }
3678
3676
  default: {
3679
3677
  throw new Error(`${loadingIndicatorType} is not known`);
3680
3678
  }
3681
3679
  }
3682
- }, [loadingIndicator, pagination?.isLoading, pagination?.pageInfo]);
3680
+ }, [resolvedLoadingIndicator, pagination?.isLoading, pagination?.pageInfo]);
3683
3681
  // Helper to determine if a specific loader index should be shown
3684
3682
  const shouldShowLoaderAtIndex = react.useCallback((loaderIndex) => {
3685
3683
  if (pagination?.isLoading === false)
3686
3684
  return false;
3687
- const { type: loadingIndicatorType } = loadingIndicator;
3685
+ const { type: loadingIndicatorType } = resolvedLoadingIndicator;
3688
3686
  let result;
3689
3687
  switch (loadingIndicatorType) {
3690
3688
  case "none":
@@ -3696,8 +3694,8 @@ const useList = ({ count, pagination, header, getItem, loadingIndicator = DEFAUL
3696
3694
  case "custom":
3697
3695
  case "skeleton": {
3698
3696
  const isInitialLoading = !pagination?.pageInfo;
3699
- const initialCount = loadingIndicator.initialLoadingCount ?? 10;
3700
- const scrollCount = loadingIndicator.scrollLoadingCount ?? 3;
3697
+ const initialCount = resolvedLoadingIndicator.initialLoadingCount ?? 10;
3698
+ const scrollCount = resolvedLoadingIndicator.scrollLoadingCount ?? 3;
3701
3699
  const maxCount = isInitialLoading ? initialCount : scrollCount;
3702
3700
  result = loaderIndex < maxCount;
3703
3701
  break;
@@ -3707,7 +3705,7 @@ const useList = ({ count, pagination, header, getItem, loadingIndicator = DEFAUL
3707
3705
  }
3708
3706
  }
3709
3707
  return result;
3710
- }, [loadingIndicator, pagination?.isLoading, pagination?.pageInfo]);
3708
+ }, [resolvedLoadingIndicator, pagination?.isLoading, pagination?.pageInfo]);
3711
3709
  const totalRowCount = react.useMemo(() => {
3712
3710
  // Only add the exact number of loading rows we want to show
3713
3711
  const loadingRows = pagination?.isLoading === true ? getLoadingRowsCount() : 0;
@@ -3831,11 +3829,19 @@ const useList = ({ count, pagination, header, getItem, loadingIndicator = DEFAUL
3831
3829
  rows,
3832
3830
  getListItemProps,
3833
3831
  header,
3834
- loadingIndicator,
3832
+ loadingIndicator: resolvedLoadingIndicator,
3835
3833
  shouldShowLoaderAtIndex,
3836
3834
  count,
3835
+ separator,
3836
+ topSeparatorOnScroll,
3837
3837
  };
3838
3838
  };
3839
+ const getResolvedLoadingIndicator = (loadingIndicator) => {
3840
+ if (typeof loadingIndicator === "function") {
3841
+ return loadingIndicator(DEFAULT_LOADING_INDICATOR_CONFIG);
3842
+ }
3843
+ return loadingIndicator;
3844
+ };
3839
3845
 
3840
3846
  // Height constants (in pixels) - based on ListItem.variants.ts styling
3841
3847
  const MIN_ITEM_HEIGHT = 56; // min-h-14 = 56px (minimum height for ListItem)
@@ -3953,7 +3959,7 @@ const cvaInteractableItem = cssClassVarianceUtilities.cvaMerge("", {
3953
3959
  * @returns {Element} ListItem component
3954
3960
  */
3955
3961
  const ListItem = ({ className, dataTestId, onClick, details, title, description, meta, thumbnail, thumbnailColor = "info-600", thumbnailBackground = "info-100", ...rest }) => {
3956
- const baseClass = cvaListItem$1({ className });
3962
+ const baseClass = cvaListItem({ className });
3957
3963
  const interactableItemClass = onClick ? tailwindMerge.twMerge(baseClass, cvaInteractableItem({ cursor: "pointer" })) : baseClass;
3958
3964
  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({
3959
3965
  className: `text-${thumbnailColor} bg-${thumbnailBackground}`,
@@ -5227,7 +5233,7 @@ exports.cvaIndicatorPing = cvaIndicatorPing;
5227
5233
  exports.cvaInteractableItem = cvaInteractableItem;
5228
5234
  exports.cvaList = cvaList;
5229
5235
  exports.cvaListContainer = cvaListContainer;
5230
- exports.cvaListItem = cvaListItem;
5236
+ exports.cvaListItem = cvaListItem$1;
5231
5237
  exports.cvaMenuItem = cvaMenuItem;
5232
5238
  exports.cvaMenuItemLabel = cvaMenuItemLabel;
5233
5239
  exports.cvaMenuItemPrefix = cvaMenuItemPrefix;
package/index.esm.js CHANGED
@@ -3422,7 +3422,31 @@ const cvaCardBodyDensityContainer = cvaMerge(["grid", "grid-cols-[1fr_auto]"], {
3422
3422
  },
3423
3423
  });
3424
3424
 
3425
- const cvaListItem$1 = cvaMerge(["py-3", "px-4", "min-h-14", "w-full", "flex", "justify-between", "items-center"]);
3425
+ const cvaListContainer = cvaMerge(["overflow-y-auto", "overflow-x-hidden", "h-full"], {
3426
+ variants: {
3427
+ withTopSeparator: {
3428
+ true: ["border-t", "border-neutral-200", "transition-colors duration-200 ease-in"],
3429
+ false: ["border-t", "border-transparent", "transition-colors duration-200 ease-in"],
3430
+ },
3431
+ },
3432
+ defaultVariants: {
3433
+ withTopSeparator: false,
3434
+ },
3435
+ });
3436
+ const cvaList = cvaMerge(["relative"]);
3437
+ const cvaListItem$1 = cvaMerge(["absolute", "top-0", "left-0", "w-full"], {
3438
+ variants: {
3439
+ separator: {
3440
+ line: ["border-b", "border-neutral-200"],
3441
+ none: "",
3442
+ },
3443
+ },
3444
+ defaultVariants: {
3445
+ separator: "none",
3446
+ },
3447
+ });
3448
+
3449
+ const cvaListItem = cvaMerge(["py-3", "px-4", "min-h-14", "w-full", "flex", "justify-between", "items-center"]);
3426
3450
  const cvaMainInformationClass = cvaMerge(["grid", "items-center", "text-sm", "gap-2"], {
3427
3451
  variants: {
3428
3452
  hasThumbnail: {
@@ -3462,33 +3486,9 @@ const ListItemSkeleton = ({ hasThumbnail = DEFAULT_SKELETON_LIST_ITEM_PROPS.hasT
3462
3486
  details: getResponsiveRandomWidthPercentage({ min: 25, max: 45 }),
3463
3487
  };
3464
3488
  });
3465
- return (jsxs("div", { className: cvaListItem$1({ className: "w-full" }), children: [jsxs("div", { className: cvaMainInformationClass({ hasThumbnail, className: "w-full" }), children: [hasThumbnail ? (jsx("div", { className: cvaThumbnailContainer({ className: "bg-gray-200" }), children: jsx("div", { className: twMerge("bg-gray-300", thumbnailShape === "circle" ? "rounded-full" : "rounded"), style: { width: 20, height: 20 } }) })) : null, jsxs("div", { className: "grid-rows-min-fr grid w-full items-center gap-1 text-sm", children: [jsx(SkeletonLines, { height: "0.875em", lines: 1, width: lineWidths.title }), hasDescription ? jsx(SkeletonLines, { height: "0.75em", lines: 1, width: lineWidths.description }) : null, hasMeta ? jsx(SkeletonLines, { height: "0.75em", lines: 1, width: lineWidths.meta }) : null] })] }), hasDetails ? (jsx("div", { className: "pl-2 text-sm", children: jsx(SkeletonLines, { height: "0.875em", lines: 1, width: lineWidths.details }) })) : null] }));
3489
+ return (jsxs("div", { className: cvaListItem({ className: "w-full" }), children: [jsxs("div", { className: cvaMainInformationClass({ hasThumbnail, className: "w-full" }), children: [hasThumbnail ? (jsx("div", { className: cvaThumbnailContainer({ className: "bg-gray-200" }), children: jsx("div", { className: twMerge("bg-gray-300", thumbnailShape === "circle" ? "rounded-full" : "rounded"), style: { width: 20, height: 20 } }) })) : null, jsxs("div", { className: "grid-rows-min-fr grid w-full items-center gap-1 text-sm", children: [jsx(SkeletonLines, { height: "0.875em", lines: 1, width: lineWidths.title }), hasDescription ? jsx(SkeletonLines, { height: "0.75em", lines: 1, width: lineWidths.description }) : null, hasMeta ? jsx(SkeletonLines, { height: "0.75em", lines: 1, width: lineWidths.meta }) : null] })] }), hasDetails ? (jsx("div", { className: "pl-2 text-sm", children: jsx(SkeletonLines, { height: "0.875em", lines: 1, width: lineWidths.details }) })) : null] }));
3466
3490
  };
3467
3491
 
3468
- const cvaListContainer = cvaMerge(["overflow-y-auto", "overflow-x-hidden", "h-full"], {
3469
- variants: {
3470
- withTopSeparator: {
3471
- true: ["border-t", "border-neutral-200", "transition-colors duration-200 ease-in"],
3472
- false: ["border-t", "border-transparent", "transition-colors duration-200 ease-in"],
3473
- },
3474
- },
3475
- defaultVariants: {
3476
- withTopSeparator: false,
3477
- },
3478
- });
3479
- const cvaList = cvaMerge(["relative"]);
3480
- const cvaListItem = cvaMerge(["absolute", "top-0", "left-0", "w-full"], {
3481
- variants: {
3482
- separator: {
3483
- line: ["[&:not(:last-child)]:border-b", "border-neutral-200"],
3484
- none: "",
3485
- },
3486
- },
3487
- defaultVariants: {
3488
- separator: "none",
3489
- },
3490
- });
3491
-
3492
3492
  /**
3493
3493
  *
3494
3494
  */
@@ -3573,9 +3573,9 @@ const ListLoadingIndicator = ({ type, hasThumbnail, thumbnailShape, hasDescripti
3573
3573
  * );
3574
3574
  * ```
3575
3575
  */
3576
- const List = ({ children, className, dataTestId, topSeparatorOnScroll = false, separator = "line",
3576
+ const List = ({ children, className, dataTestId,
3577
3577
  // UseListResult properties
3578
- containerRef, listRef, rows, getListItemProps, header, loadingIndicator, shouldShowLoaderAtIndex, count, isScrolling, scrollOffset,
3578
+ containerRef, listRef, rows, getListItemProps, header, loadingIndicator, shouldShowLoaderAtIndex, count, isScrolling, scrollOffset, separator, topSeparatorOnScroll,
3579
3579
  // Unused but part of UseListResult interface
3580
3580
  getTotalSize: _getTotalSize, getVirtualItems: _getVirtualItems, scrollToOffset: _scrollToOffset, scrollToIndex: _scrollToIndex, measure: _measure, }) => {
3581
3581
  return (jsx("div", { className: cvaListContainer({
@@ -3584,22 +3584,18 @@ getTotalSize: _getTotalSize, getVirtualItems: _getVirtualItems, scrollToOffset:
3584
3584
  }), "data-is-scrolling": isScrolling, "data-testid": dataTestId, ref: containerRef, children: jsx("ul", { className: cvaList(), ref: listRef, children: rows.map(row => {
3585
3585
  // Generate list item props with separator styling
3586
3586
  const listItemProps = getListItemProps(row, {
3587
- className: cvaListItem({ separator }),
3587
+ className: cvaListItem$1({ separator }),
3588
3588
  });
3589
3589
  const key = row.virtualRow.key;
3590
3590
  // Render loading row
3591
3591
  if (row.type === "loader") {
3592
- const loadingConfig = loadingIndicator ?? {
3593
- type: "skeleton",
3594
- ...DEFAULT_SKELETON_LIST_ITEM_PROPS,
3595
- };
3596
3592
  // Use the total data count from useList, not visible rows (which are virtualized)
3597
3593
  const hasHeaderRow = !!header;
3598
3594
  const dataStartIndex = hasHeaderRow ? 1 : 0;
3599
3595
  const totalDataRows = dataStartIndex + count;
3600
3596
  const loaderIndex = row.virtualRow.index - totalDataRows;
3601
3597
  const shouldShowLoader = shouldShowLoaderAtIndex(loaderIndex);
3602
- return (jsx("li", { ...listItemProps, children: shouldShowLoader ? jsx(ListLoadingIndicator, { ...loadingConfig }) : null }, key));
3598
+ return (jsx("li", { ...listItemProps, children: shouldShowLoader ? jsx(ListLoadingIndicator, { ...loadingIndicator }) : null }, key));
3603
3599
  }
3604
3600
  // Render header row
3605
3601
  if (row.type === "header") {
@@ -3647,10 +3643,12 @@ const DEFAULT_LOADING_INDICATOR_CONFIG = {
3647
3643
  * );
3648
3644
  * ```
3649
3645
  */
3650
- const useList = ({ count, pagination, header, getItem, loadingIndicator = DEFAULT_LOADING_INDICATOR_CONFIG, onRowClick, onChange, estimateItemSize, estimateHeaderSize, overscan, }) => {
3646
+ const useList = ({ count, pagination, header, getItem, loadingIndicator = DEFAULT_LOADING_INDICATOR_CONFIG, onRowClick, onChange, estimateItemSize, estimateHeaderSize, overscan, separator = "line", topSeparatorOnScroll = false, }) => {
3651
3647
  const containerRef = useRef(null);
3652
3648
  const listRef = useRef(null);
3653
3649
  const rowRefsMap = useRef(new Map());
3650
+ // Resolve loading indicator once to avoid unnecessary re-renders
3651
+ const resolvedLoadingIndicator = useMemo(() => getResolvedLoadingIndicator(loadingIndicator), [loadingIndicator]);
3654
3652
  // Calculate total count including header
3655
3653
  const hasHeader = Boolean(header);
3656
3654
  const dataStartIndex = hasHeader ? 1 : 0;
@@ -3660,7 +3658,7 @@ const useList = ({ count, pagination, header, getItem, loadingIndicator = DEFAUL
3660
3658
  const getLoadingRowsCount = useCallback(() => {
3661
3659
  if (pagination?.isLoading === false)
3662
3660
  return 0;
3663
- const { type: loadingIndicatorType } = loadingIndicator;
3661
+ const { type: loadingIndicatorType } = resolvedLoadingIndicator;
3664
3662
  switch (loadingIndicatorType) {
3665
3663
  case "none":
3666
3664
  return 0;
@@ -3669,20 +3667,20 @@ const useList = ({ count, pagination, header, getItem, loadingIndicator = DEFAUL
3669
3667
  case "custom":
3670
3668
  case "skeleton": {
3671
3669
  const isInitialLoading = !pagination?.pageInfo;
3672
- const initialCount = loadingIndicator.initialLoadingCount ?? 10;
3673
- const scrollCount = loadingIndicator.scrollLoadingCount ?? 3;
3670
+ const initialCount = resolvedLoadingIndicator.initialLoadingCount ?? 10;
3671
+ const scrollCount = resolvedLoadingIndicator.scrollLoadingCount ?? 3;
3674
3672
  return isInitialLoading ? initialCount : scrollCount;
3675
3673
  }
3676
3674
  default: {
3677
3675
  throw new Error(`${loadingIndicatorType} is not known`);
3678
3676
  }
3679
3677
  }
3680
- }, [loadingIndicator, pagination?.isLoading, pagination?.pageInfo]);
3678
+ }, [resolvedLoadingIndicator, pagination?.isLoading, pagination?.pageInfo]);
3681
3679
  // Helper to determine if a specific loader index should be shown
3682
3680
  const shouldShowLoaderAtIndex = useCallback((loaderIndex) => {
3683
3681
  if (pagination?.isLoading === false)
3684
3682
  return false;
3685
- const { type: loadingIndicatorType } = loadingIndicator;
3683
+ const { type: loadingIndicatorType } = resolvedLoadingIndicator;
3686
3684
  let result;
3687
3685
  switch (loadingIndicatorType) {
3688
3686
  case "none":
@@ -3694,8 +3692,8 @@ const useList = ({ count, pagination, header, getItem, loadingIndicator = DEFAUL
3694
3692
  case "custom":
3695
3693
  case "skeleton": {
3696
3694
  const isInitialLoading = !pagination?.pageInfo;
3697
- const initialCount = loadingIndicator.initialLoadingCount ?? 10;
3698
- const scrollCount = loadingIndicator.scrollLoadingCount ?? 3;
3695
+ const initialCount = resolvedLoadingIndicator.initialLoadingCount ?? 10;
3696
+ const scrollCount = resolvedLoadingIndicator.scrollLoadingCount ?? 3;
3699
3697
  const maxCount = isInitialLoading ? initialCount : scrollCount;
3700
3698
  result = loaderIndex < maxCount;
3701
3699
  break;
@@ -3705,7 +3703,7 @@ const useList = ({ count, pagination, header, getItem, loadingIndicator = DEFAUL
3705
3703
  }
3706
3704
  }
3707
3705
  return result;
3708
- }, [loadingIndicator, pagination?.isLoading, pagination?.pageInfo]);
3706
+ }, [resolvedLoadingIndicator, pagination?.isLoading, pagination?.pageInfo]);
3709
3707
  const totalRowCount = useMemo(() => {
3710
3708
  // Only add the exact number of loading rows we want to show
3711
3709
  const loadingRows = pagination?.isLoading === true ? getLoadingRowsCount() : 0;
@@ -3829,11 +3827,19 @@ const useList = ({ count, pagination, header, getItem, loadingIndicator = DEFAUL
3829
3827
  rows,
3830
3828
  getListItemProps,
3831
3829
  header,
3832
- loadingIndicator,
3830
+ loadingIndicator: resolvedLoadingIndicator,
3833
3831
  shouldShowLoaderAtIndex,
3834
3832
  count,
3833
+ separator,
3834
+ topSeparatorOnScroll,
3835
3835
  };
3836
3836
  };
3837
+ const getResolvedLoadingIndicator = (loadingIndicator) => {
3838
+ if (typeof loadingIndicator === "function") {
3839
+ return loadingIndicator(DEFAULT_LOADING_INDICATOR_CONFIG);
3840
+ }
3841
+ return loadingIndicator;
3842
+ };
3837
3843
 
3838
3844
  // Height constants (in pixels) - based on ListItem.variants.ts styling
3839
3845
  const MIN_ITEM_HEIGHT = 56; // min-h-14 = 56px (minimum height for ListItem)
@@ -3951,7 +3957,7 @@ const cvaInteractableItem = cvaMerge("", {
3951
3957
  * @returns {Element} ListItem component
3952
3958
  */
3953
3959
  const ListItem = ({ className, dataTestId, onClick, details, title, description, meta, thumbnail, thumbnailColor = "info-600", thumbnailBackground = "info-100", ...rest }) => {
3954
- const baseClass = cvaListItem$1({ className });
3960
+ const baseClass = cvaListItem({ className });
3955
3961
  const interactableItemClass = onClick ? twMerge(baseClass, cvaInteractableItem({ cursor: "pointer" })) : baseClass;
3956
3962
  return (jsxs("li", { className: interactableItemClass, "data-testid": dataTestId, onClick: onClick, ...rest, children: [jsxs("div", { className: cvaMainInformationClass({ hasThumbnail: !!thumbnail }), children: [thumbnail ? (jsx("div", { className: cvaThumbnailContainer({
3957
3963
  className: `text-${thumbnailColor} bg-${thumbnailBackground}`,
@@ -5145,4 +5151,4 @@ const cvaClickable = cvaMerge([
5145
5151
  },
5146
5152
  });
5147
5153
 
5148
- export { ActionRenderer, Alert, Badge, Breadcrumb, BreadcrumbContainer, Button, Card, CardBody, CardFooter, CardHeader, Collapse, CompletionStatusIndicator, CopyableText, DetailsList, EmptyState, EmptyValue, ExternalLink, Heading, Highlight, HorizontalOverflowScroller, Icon, IconButton, Indicator, KPI, KPICard, List, ListItem, MenuDivider, MenuItem, MenuList, MoreMenu, Notice, PackageNameStoryComponent, Page, PageContent, PageHeader, PageHeaderKpiMetrics, PageHeaderSecondaryActions, PageHeaderTitle, Pagination, Polygon, Popover, PopoverContent, PopoverTitle, PopoverTrigger, Portal, Prompt, ROLE_CARD, SectionHeader, Sidebar, SkeletonLines, Spacer, Spinner, StarButton, Tab, TabContent, TabList, Tabs, Tag, Text, ToggleGroup, Tooltip, ValueBar, ZStack, cvaButton, cvaButtonPrefixSuffix, cvaButtonSpinner, cvaButtonSpinnerContainer, cvaClickable, cvaContainerStyles, cvaIconButton, cvaImgStyles, cvaIndicator, cvaIndicatorIcon, cvaIndicatorIconBackground, cvaIndicatorLabel, cvaIndicatorPing, cvaInteractableItem, cvaList, cvaListContainer, cvaListItem, cvaMenuItem, cvaMenuItemLabel, cvaMenuItemPrefix, cvaMenuItemStyle, cvaMenuItemSuffix, cvaPageHeader, cvaPageHeaderContainer, cvaPageHeaderHeading, cvaToggleGroup, cvaToggleGroupWithSlidingBackground, cvaToggleItem, cvaToggleItemContent, cvaToggleItemText, cvaZStackContainer, cvaZStackItem, defaultPageSize, docs, getDevicePixelRatio, getResponsiveRandomWidthPercentage, getValueBarColorByValue, iconColorNames, iconPalette, noPagination, useClickOutside, useContainerBreakpoints, useContinuousTimeout, useDebounce, useDevicePixelRatio, useElevatedReducer, useElevatedState, useGeometry, useHover, useInfiniteScroll, useIsFirstRender, useIsFullscreen, useIsTextTruncated, useList, useListItemHeight, useModifierKey, useOverflowItems, usePopoverContext, usePrompt, useRelayPagination, useResize, useScrollDetection, useSelfUpdatingRef, useStable, useTimeout, useViewportBreakpoints, useWatch, useWindowActivity };
5154
+ export { ActionRenderer, Alert, Badge, Breadcrumb, BreadcrumbContainer, Button, Card, CardBody, CardFooter, CardHeader, Collapse, CompletionStatusIndicator, CopyableText, DetailsList, EmptyState, EmptyValue, ExternalLink, Heading, Highlight, HorizontalOverflowScroller, Icon, IconButton, Indicator, KPI, KPICard, List, ListItem, MenuDivider, MenuItem, MenuList, MoreMenu, Notice, PackageNameStoryComponent, Page, PageContent, PageHeader, PageHeaderKpiMetrics, PageHeaderSecondaryActions, PageHeaderTitle, Pagination, Polygon, Popover, PopoverContent, PopoverTitle, PopoverTrigger, Portal, Prompt, ROLE_CARD, SectionHeader, Sidebar, SkeletonLines, Spacer, Spinner, StarButton, Tab, TabContent, TabList, Tabs, Tag, Text, ToggleGroup, Tooltip, ValueBar, ZStack, cvaButton, cvaButtonPrefixSuffix, cvaButtonSpinner, cvaButtonSpinnerContainer, cvaClickable, cvaContainerStyles, cvaIconButton, cvaImgStyles, cvaIndicator, cvaIndicatorIcon, cvaIndicatorIconBackground, cvaIndicatorLabel, cvaIndicatorPing, cvaInteractableItem, cvaList, cvaListContainer, cvaListItem$1 as cvaListItem, cvaMenuItem, cvaMenuItemLabel, cvaMenuItemPrefix, cvaMenuItemStyle, cvaMenuItemSuffix, cvaPageHeader, cvaPageHeaderContainer, cvaPageHeaderHeading, cvaToggleGroup, cvaToggleGroupWithSlidingBackground, cvaToggleItem, cvaToggleItemContent, cvaToggleItemText, cvaZStackContainer, cvaZStackItem, defaultPageSize, docs, getDevicePixelRatio, getResponsiveRandomWidthPercentage, getValueBarColorByValue, iconColorNames, iconPalette, noPagination, useClickOutside, useContainerBreakpoints, useContinuousTimeout, useDebounce, useDevicePixelRatio, useElevatedReducer, useElevatedState, useGeometry, useHover, useInfiniteScroll, useIsFirstRender, useIsFullscreen, useIsTextTruncated, useList, useListItemHeight, useModifierKey, useOverflowItems, usePopoverContext, usePrompt, useRelayPagination, useResize, useScrollDetection, useSelfUpdatingRef, useStable, useTimeout, useViewportBreakpoints, useWatch, useWindowActivity };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trackunit/react-components",
3
- "version": "1.9.23",
3
+ "version": "1.9.26",
4
4
  "repository": "https://github.com/Trackunit/manager",
5
5
  "license": "SEE LICENSE IN LICENSE.txt",
6
6
  "engines": {
@@ -16,11 +16,11 @@
16
16
  "@floating-ui/react": "^0.26.25",
17
17
  "string-ts": "^2.0.0",
18
18
  "tailwind-merge": "^2.0.0",
19
- "@trackunit/ui-design-tokens": "1.7.14",
20
- "@trackunit/css-class-variance-utilities": "1.7.13",
21
- "@trackunit/shared-utils": "1.9.13",
22
- "@trackunit/ui-icons": "1.7.15",
23
- "@trackunit/react-test-setup": "1.4.13",
19
+ "@trackunit/ui-design-tokens": "1.7.17",
20
+ "@trackunit/css-class-variance-utilities": "1.7.16",
21
+ "@trackunit/shared-utils": "1.9.16",
22
+ "@trackunit/ui-icons": "1.7.18",
23
+ "@trackunit/react-test-setup": "1.4.16",
24
24
  "@tanstack/react-router": "1.114.29",
25
25
  "es-toolkit": "^1.39.10",
26
26
  "@tanstack/react-virtual": "3.13.12"
@@ -1,9 +1,6 @@
1
- import { VariantProps } from "@trackunit/css-class-variance-utilities";
2
1
  import { ReactElement } from "react";
3
2
  import { CommonProps } from "../../common/CommonProps";
4
- import { cvaListItem } from "./List.variants";
5
3
  import { UseListResult, VirtualizationListItemProps } from "./useList";
6
- type Separator = NonNullable<VariantProps<typeof cvaListItem>["separator"]>;
7
4
  export type { VirtualizationListItemProps } from "./useList";
8
5
  export interface ListProps<TItem = unknown> extends CommonProps, UseListResult<TItem> {
9
6
  /**
@@ -26,14 +23,6 @@ export interface ListProps<TItem = unknown> extends CommonProps, UseListResult<T
26
23
  item: TItem | undefined;
27
24
  index: number;
28
25
  }) => ReactElement | null;
29
- /**
30
- * Separator style between list items.
31
- */
32
- separator?: Separator;
33
- /**
34
- * Show a top separator when the list is scrolled.
35
- */
36
- topSeparatorOnScroll?: boolean;
37
26
  }
38
27
  /**
39
28
  * A performant virtualized list component with infinite scrolling support.
@@ -100,4 +89,4 @@ export interface ListProps<TItem = unknown> extends CommonProps, UseListResult<T
100
89
  * );
101
90
  * ```
102
91
  */
103
- export declare const List: <TItem = unknown>({ children, className, dataTestId, topSeparatorOnScroll, separator, containerRef, listRef, rows, getListItemProps, header, loadingIndicator, shouldShowLoaderAtIndex, count, isScrolling, scrollOffset, getTotalSize: _getTotalSize, getVirtualItems: _getVirtualItems, scrollToOffset: _scrollToOffset, scrollToIndex: _scrollToIndex, measure: _measure, }: ListProps<TItem>) => ReactElement;
92
+ export declare const List: <TItem = unknown>({ children, className, dataTestId, containerRef, listRef, rows, getListItemProps, header, loadingIndicator, shouldShowLoaderAtIndex, count, isScrolling, scrollOffset, separator, topSeparatorOnScroll, getTotalSize: _getTotalSize, getVirtualItems: _getVirtualItems, scrollToOffset: _scrollToOffset, scrollToIndex: _scrollToIndex, measure: _measure, }: ListProps<TItem>) => ReactElement;
@@ -2,6 +2,9 @@ import type { VirtualItem, Virtualizer } from "@tanstack/react-virtual";
2
2
  import { ReactElement, RefObject } from "react";
3
3
  import { RelayPagination } from "../../hooks";
4
4
  import { ListLoadingIndicatorProps } from "./ListLoadingIndicator";
5
+ declare const DEFAULT_LOADING_INDICATOR_CONFIG: Extract<ListLoadingIndicatorProps, {
6
+ type: "skeleton";
7
+ }>;
5
8
  /**
6
9
  * Props that must be spread onto list items for proper virtualization.
7
10
  * These handle positioning, measurement, accessibility, and interaction.
@@ -12,7 +15,7 @@ export interface VirtualizationListItemProps {
12
15
  /** CSS classes for list styling and separators */
13
16
  className: string;
14
17
  /** Element ref for virtualization measurement and setting positioning transform styles */
15
- ref?: (node: HTMLLIElement | null) => void;
18
+ ref: (node: HTMLLIElement | null) => void;
16
19
  /** Click handler for row-level interactions */
17
20
  onClick?: () => void;
18
21
  /** Data attribute for accessibility and debugging */
@@ -45,7 +48,7 @@ export interface UseListOptions<TItem = unknown> {
45
48
  /**
46
49
  * Loading indicator configuration for pagination loading.
47
50
  */
48
- loadingIndicator?: ListLoadingIndicatorProps;
51
+ loadingIndicator?: ListLoadingIndicatorProps | ((defaultLoadingIndicator: typeof DEFAULT_LOADING_INDICATOR_CONFIG) => ListLoadingIndicatorProps);
49
52
  /**
50
53
  * Callback fired when a row is clicked.
51
54
  */
@@ -97,6 +100,14 @@ export interface UseListOptions<TItem = unknown> {
97
100
  * Higher values reduce blank areas during fast scrolling but increase memory usage.
98
101
  */
99
102
  overscan?: number;
103
+ /**
104
+ * Separator style between list items.
105
+ */
106
+ separator?: "none" | "line";
107
+ /**
108
+ * Show a top separator when the list is scrolled.
109
+ */
110
+ topSeparatorOnScroll?: boolean;
100
111
  }
101
112
  export type ListRowType = "header" | "data" | "loader";
102
113
  export type ListRow<TItem> = {
@@ -130,11 +141,15 @@ export interface UseListResult<TItem> extends Pick<Virtualizer<HTMLDivElement, H
130
141
  /** Header element (if provided) */
131
142
  readonly header?: ReactElement;
132
143
  /** Loading indicator configuration */
133
- readonly loadingIndicator?: ListLoadingIndicatorProps;
144
+ readonly loadingIndicator: ListLoadingIndicatorProps;
134
145
  /** Helper to determine if a specific loader index should be shown */
135
146
  readonly shouldShowLoaderAtIndex: (loaderIndex: number) => boolean;
136
147
  /** Total number of data items (for consistent calculations) */
137
148
  readonly count: number;
149
+ /** Separator style between list items */
150
+ readonly separator: "none" | "line";
151
+ /** Show a top separator when the list is scrolled */
152
+ readonly topSeparatorOnScroll: boolean;
138
153
  }
139
154
  /**
140
155
  * A hook for managing virtualized list state and behavior.
@@ -168,4 +183,5 @@ export interface UseListResult<TItem> extends Pick<Virtualizer<HTMLDivElement, H
168
183
  * );
169
184
  * ```
170
185
  */
171
- export declare const useList: <TItem = unknown>({ count, pagination, header, getItem, loadingIndicator, onRowClick, onChange, estimateItemSize, estimateHeaderSize, overscan, }: UseListOptions<TItem>) => UseListResult<TItem>;
186
+ export declare const useList: <TItem = unknown>({ count, pagination, header, getItem, loadingIndicator, onRowClick, onChange, estimateItemSize, estimateHeaderSize, overscan, separator, topSeparatorOnScroll, }: UseListOptions<TItem>) => UseListResult<TItem>;
187
+ export {};
@@ -1,11 +1,21 @@
1
1
  import { VariantProps } from "@trackunit/css-class-variance-utilities";
2
2
  import { MappedOmit } from "@trackunit/shared-utils";
3
3
  import { tailwindPalette, ThemeColors } from "@trackunit/ui-design-tokens";
4
- import { CSSProperties, MouseEventHandler, ReactElement, ReactNode, Ref } from "react";
4
+ import { MouseEventHandler, ReactElement, ReactNode, Ref } from "react";
5
5
  import { CommonProps } from "../../common/CommonProps";
6
6
  import { cvaListItem } from "./ListItem.variants";
7
7
  type ThemeColorShades = `${keyof (typeof tailwindPalette)[keyof typeof tailwindPalette]}`;
8
- export interface ListItemProps extends CommonProps, MappedOmit<VariantProps<typeof cvaListItem>, "className"> {
8
+ export type ListItemVirtualizationProps = {
9
+ /** @internal */
10
+ ref?: Ref<HTMLLIElement>;
11
+ /** @internal */
12
+ tabIndex?: number;
13
+ /** @internal */
14
+ "data-index"?: number;
15
+ /** @internal */
16
+ className?: string;
17
+ };
18
+ export interface ListItemProps extends CommonProps, ListItemVirtualizationProps, MappedOmit<VariantProps<typeof cvaListItem>, "className"> {
9
19
  /**The main text line of the ListItem */
10
20
  title: string | ReactElement<CommonProps>;
11
21
  /**Optional description for the ListItem. Can be used for descriptions, metadata, or other important details. */
@@ -24,14 +34,6 @@ export interface ListItemProps extends CommonProps, MappedOmit<VariantProps<type
24
34
  * If asset image is chosen as thumbnail, make the thumbnailBackground "white".
25
35
  */
26
36
  thumbnailBackground?: `${ThemeColors}-${ThemeColorShades}` | `${ThemeColors}`;
27
- /** @internal */
28
- style?: CSSProperties;
29
- /** @internal */
30
- ref?: Ref<HTMLLIElement>;
31
- /** @internal */
32
- tabIndex?: number;
33
- /** @internal */
34
- "data-index"?: number;
35
37
  }
36
38
  /**
37
39
  * 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.