@trackunit/react-components 1.22.27 → 1.23.2

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
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var jsxRuntime = require('react/jsx-runtime');
4
+ var tailwindMerge = require('tailwind-merge');
4
5
  var sharedUtils = require('@trackunit/shared-utils');
5
6
  var react = require('react');
6
7
  var uiDesignTokens = require('@trackunit/ui-design-tokens');
@@ -15,7 +16,6 @@ var reactSlot = require('@radix-ui/react-slot');
15
16
  var reactRouter = require('@tanstack/react-router');
16
17
  var esToolkit = require('es-toolkit');
17
18
  var react$1 = require('@floating-ui/react');
18
- var tailwindMerge = require('tailwind-merge');
19
19
  var reactVirtual = require('@tanstack/react-virtual');
20
20
  var reactHelmetAsync = require('react-helmet-async');
21
21
  var reactTabs = require('@radix-ui/react-tabs');
@@ -359,7 +359,7 @@ const buildLibraryHref = (slug) => {
359
359
  * A component used to display the package name and version in the Storybook docs.
360
360
  * The package-name tag links to the matching section on the "Libraries" overview page.
361
361
  */
362
- const PackageNameStoryComponent = ({ packageJSON }) => {
362
+ const PackageNameStoryComponent = ({ packageJSON, className, "data-testid": dataTestId, style, ref, }) => {
363
363
  const name = packageJSON?.name;
364
364
  const slug = name ? toLibrarySlug(name) : undefined;
365
365
  const href = slug ? buildLibraryHref(slug) : undefined;
@@ -373,7 +373,7 @@ const PackageNameStoryComponent = ({ packageJSON }) => {
373
373
  if (top)
374
374
  top.location.href = href;
375
375
  };
376
- return (jsxRuntime.jsxs("div", { className: "flex gap-2", children: [href ? (jsxRuntime.jsx("a", { "aria-label": `Learn more about ${name}`, className: "no-underline", href: href, onClick: onClick, target: "_top", title: `Learn more about ${name}`, children: jsxRuntime.jsx(Tag, { color: "neutral", children: name }) })) : (jsxRuntime.jsx(Tag, { color: "neutral", children: name })), packageJSON?.version ? jsxRuntime.jsxs(Tag, { color: "neutral", children: ["v", packageJSON.version] }) : null] }));
376
+ return (jsxRuntime.jsxs("div", { className: tailwindMerge.twMerge("flex gap-2", className), "data-testid": dataTestId, ref: ref, style: style, children: [href ? (jsxRuntime.jsx("a", { "aria-label": `Learn more about ${name}`, className: "no-underline", href: href, onClick: onClick, target: "_top", title: `Learn more about ${name}`, children: jsxRuntime.jsx(Tag, { color: "neutral", children: name }) })) : (jsxRuntime.jsx(Tag, { color: "neutral", children: name })), packageJSON?.version ? jsxRuntime.jsxs(Tag, { color: "neutral", children: ["v", packageJSON.version] }) : null] }));
377
377
  };
378
378
 
379
379
  const docs = {
@@ -1348,6 +1348,19 @@ const Badge = ({ color = "primary", size = "default", compact = false, className
1348
1348
  return (jsxRuntime.jsx("span", { className: cvaBadge({ color, size, className, compact, isSingleChar }), "data-testid": dataTestId, ref: ref, style: style, children: compact ? null : displayedCount }));
1349
1349
  };
1350
1350
 
1351
+ const cvaBreadcrumb = cssClassVarianceUtilities.cvaMerge(["my-4", "flex", "place-items-center"]);
1352
+ const cvaBreadcrumbItem = cssClassVarianceUtilities.cvaMerge(["flex", "items-center"]);
1353
+ const cvaBreadcrumbForLargeScreen = cssClassVarianceUtilities.cvaMerge(["flex", "flex-nowrap", "items-center"]);
1354
+ const cvaBreadcrumbForMediumScreen = cssClassVarianceUtilities.cvaMerge(["flex", "items-center"], {
1355
+ variants: {
1356
+ expanded: {
1357
+ true: "flex-wrap",
1358
+ false: "flex-nowrap",
1359
+ },
1360
+ },
1361
+ defaultVariants: { expanded: false },
1362
+ });
1363
+
1351
1364
  /**
1352
1365
  * Maps size keys to their corresponding state property names.
1353
1366
  */
@@ -1445,19 +1458,6 @@ const useViewportBreakpoints = (options = {}) => {
1445
1458
  return viewportSize;
1446
1459
  };
1447
1460
 
1448
- const cvaBreadcrumb = cssClassVarianceUtilities.cvaMerge(["my-4", "flex", "place-items-center"]);
1449
- const cvaBreadcrumbItem = cssClassVarianceUtilities.cvaMerge(["flex", "items-center"]);
1450
- const cvaBreadcrumbForLargeScreen = cssClassVarianceUtilities.cvaMerge(["flex", "flex-nowrap", "items-center"]);
1451
- const cvaBreadcrumbForMediumScreen = cssClassVarianceUtilities.cvaMerge(["flex", "items-center"], {
1452
- variants: {
1453
- expanded: {
1454
- true: "flex-wrap",
1455
- false: "flex-nowrap",
1456
- },
1457
- },
1458
- defaultVariants: { expanded: false },
1459
- });
1460
-
1461
1461
  /**
1462
1462
  * BreadcrumbItem is a helper component that renders the individual breadcrumb item.
1463
1463
  */
@@ -1500,6 +1500,23 @@ const BreadcrumbForSmallScreen = ({ "data-testid": dataTestId, breadcrumbItems,
1500
1500
  return (jsxRuntime.jsx("div", { className: className, "data-testid": dataTestId, style: style, children: lastBreadcrumbItem.map((item, index) => (jsxRuntime.jsx(BreadcrumbItem, { "data-testid": `breadcrumbItem-${dataTestId}`, item: item }, `breadcrumbItem-${index}`))) }));
1501
1501
  };
1502
1502
 
1503
+ /**
1504
+ * BreadcrumbContainer is a helper component that renders the breadcrumb items based on the screen size.
1505
+ *
1506
+ * @param {BreadcrumbContainerProps} props - The props for the BreadcrumbContainer component
1507
+ */
1508
+ const BreadcrumbContainer = ({ className, "data-testid": dataTestId, breadcrumbItems, style, ref, }) => {
1509
+ const { isMd: isMediumScreen, isXs: isSmallScreen } = useViewportBreakpoints();
1510
+ const sharedContainerProps = { className, "data-testid": dataTestId, ref, style };
1511
+ if (isMediumScreen) {
1512
+ return (jsxRuntime.jsx("div", { ...sharedContainerProps, children: jsxRuntime.jsx(BreadcrumbForMediumScreen, { breadcrumbItems: breadcrumbItems, "data-testid": `mediumScreen-${dataTestId}` }) }));
1513
+ }
1514
+ if (isSmallScreen) {
1515
+ return (jsxRuntime.jsx("div", { ...sharedContainerProps, children: jsxRuntime.jsx(BreadcrumbForSmallScreen, { breadcrumbItems: breadcrumbItems, "data-testid": `smallScreen-${dataTestId}` }) }));
1516
+ }
1517
+ return (jsxRuntime.jsx("div", { ...sharedContainerProps, children: jsxRuntime.jsx(BreadcrumbForLargeScreen, { breadcrumbItems: breadcrumbItems, "data-testid": `largeScreen-${dataTestId}` }) }));
1518
+ };
1519
+
1503
1520
  /**
1504
1521
  * useBreadcrumbItemsToRender is a custom hook that takes an array of BreadcrumbItemProps and returns an array of React.ReactElement
1505
1522
  */
@@ -1553,21 +1570,6 @@ const Breadcrumb = ({ className, "data-testid": dataTestId, breadcrumbItems, onC
1553
1570
  const breadCrumbItemsToJSX = useBreadcrumbItemsToRender(breadcrumbItems);
1554
1571
  return (jsxRuntime.jsxs("div", { className: cvaBreadcrumb({ className }), "data-testid": dataTestId, ref: ref, style: style, children: [jsxRuntime.jsx(IconButton, { "data-testid": `backButton-${dataTestId}`, icon: jsxRuntime.jsx(Icon, { name: "ArrowLeft", size: "small" }), onClick: onClickBack, size: "small", variant: "ghost-neutral" }), jsxRuntime.jsx("div", { children: jsxRuntime.jsx(BreadcrumbContainer, { breadcrumbItems: breadCrumbItemsToJSX, "data-testid": dataTestId }) })] }));
1555
1572
  };
1556
- /**
1557
- * BreadcrumbContainer is a helper component that renders the breadcrumb items based on the screen size.
1558
- *
1559
- * @param {BreadcrumbContainerProps} props - The props for the BreadcrumbContainer component
1560
- */
1561
- const BreadcrumbContainer = ({ "data-testid": dataTestId, breadcrumbItems, }) => {
1562
- const { isMd: isMediumScreen, isXs: isSmallScreen } = useViewportBreakpoints();
1563
- if (isMediumScreen) {
1564
- return jsxRuntime.jsx(BreadcrumbForMediumScreen, { breadcrumbItems: breadcrumbItems, "data-testid": `mediumScreen-${dataTestId}` });
1565
- }
1566
- if (isSmallScreen) {
1567
- return jsxRuntime.jsx(BreadcrumbForSmallScreen, { breadcrumbItems: breadcrumbItems, "data-testid": `smallScreen-${dataTestId}` });
1568
- }
1569
- return jsxRuntime.jsx(BreadcrumbForLargeScreen, { breadcrumbItems: breadcrumbItems, "data-testid": `largeScreen-${dataTestId}` });
1570
- };
1571
1573
 
1572
1574
  /**
1573
1575
  * StarButton renders a clickable star icon for toggling favorite/starred state.
@@ -1592,8 +1594,8 @@ const BreadcrumbContainer = ({ "data-testid": dataTestId, breadcrumbItems, }) =>
1592
1594
  * @param {StarButtonProps} props - The props for the StarButton component
1593
1595
  * @returns {ReactElement} StarButton component
1594
1596
  */
1595
- const StarButton = ({ starred, onClick, ref }) => {
1596
- return (jsxRuntime.jsx("div", { "data-test-id": "starred-filter", onClick: onClick, ref: ref, children: jsxRuntime.jsx(Icon, { color: starred ? "primary" : "neutral", name: "Star", size: "medium" }) }));
1597
+ const StarButton = ({ starred, onClick, className, "data-testid": dataTestId, style, ref, }) => {
1598
+ return (jsxRuntime.jsx("div", { className: className, "data-testid": dataTestId ?? "starred-filter", onClick: onClick, ref: ref, style: style, children: jsxRuntime.jsx(Icon, { color: starred ? "primary" : "neutral", name: "Star", size: "medium" }) }));
1597
1599
  };
1598
1600
 
1599
1601
  const cvaCard = cssClassVarianceUtilities.cvaMerge([
@@ -3685,9 +3687,10 @@ function createGrid() {
3685
3687
  * }
3686
3688
  * ```
3687
3689
  */
3688
- function GridAreas({ slots, css, containerProps, validationRef, className, style, children, asChild = false, }) {
3690
+ function GridAreas({ slots, css, containerProps, validationRef, className, "data-testid": dataTestId, ref, style, children, asChild = false, }) {
3689
3691
  const Comp = asChild ? reactSlot.Slot : "div";
3690
- return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("style", { children: css }), jsxRuntime.jsx(Comp, { ref: validationRef, ...containerProps, className: tailwindMerge.twMerge("@container grid", className), style: style, children: children(slots) })] }));
3692
+ const mergedRef = useMergeRefs([validationRef, ref]);
3693
+ return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("style", { children: css }), jsxRuntime.jsx(Comp, { ...containerProps, className: tailwindMerge.twMerge("@container grid", className), "data-testid": dataTestId, ref: mergedRef, style: style, children: children(slots) })] }));
3691
3694
  }
3692
3695
 
3693
3696
  /**
@@ -5666,15 +5669,16 @@ const ListLoadingIndicator = ({ type, hasThumbnail, thumbnailShape, hasDescripti
5666
5669
  * );
5667
5670
  * ```
5668
5671
  */
5669
- const List = ({ children, className, "data-testid": dataTestId, style,
5672
+ const List = ({ children, className, "data-testid": dataTestId, style, ref,
5670
5673
  // UseListResult properties
5671
5674
  containerRef, listRef, rows, getListItemProps, header, loadingIndicator, shouldShowLoaderAtIndex, count, isScrolling, separator, topSeparatorOnScroll, isAtTop, contentFillsContainer,
5672
5675
  // Unused but part of UseListResult interface (can be used from parent)
5673
5676
  scrollOffset: _scrollOffset, getTotalSize: _getTotalSize, getVirtualItems: _getVirtualItems, scrollToOffset: _scrollToOffset, scrollToIndex: _scrollToIndex, measure: _measure, }) => {
5677
+ const mergedContainerRef = useMergeRefs([containerRef, ref]);
5674
5678
  return (jsxRuntime.jsx("div", { className: cvaListContainer({
5675
5679
  withTopSeparator: topSeparatorOnScroll && !isAtTop,
5676
5680
  className,
5677
- }), "data-is-scrolling": isScrolling, "data-testid": dataTestId, ref: containerRef, style: style, children: jsxRuntime.jsx("ul", { className: cvaList(), ref: listRef, children: rows.map(row => {
5681
+ }), "data-is-scrolling": isScrolling, "data-testid": dataTestId, ref: mergedContainerRef, style: style, children: jsxRuntime.jsx("ul", { className: cvaList(), ref: listRef, children: rows.map(row => {
5678
5682
  // Generate list item props with clean separator styling
5679
5683
  const listItemProps = getListItemProps(row, {
5680
5684
  className: cvaListItem$1({
@@ -6216,8 +6220,8 @@ const cvaMenuListItem = cssClassVarianceUtilities.cvaMerge("max-w-full");
6216
6220
  * ```
6217
6221
  * @returns {ReactElement} MenuDivider component
6218
6222
  */
6219
- const MenuDivider = ({ style }) => {
6220
- return jsxRuntime.jsx("div", { className: cvaMenuListDivider(), "data-testid": "menu-divider", style: style });
6223
+ const MenuDivider = ({ className, style, "data-testid": dataTestId, ref }) => {
6224
+ return (jsxRuntime.jsx("div", { className: cvaMenuListDivider({ className }), "data-testid": dataTestId ?? "menu-divider", ref: ref, style: style }));
6221
6225
  };
6222
6226
 
6223
6227
  /**
@@ -6883,6 +6887,7 @@ function ActionRenderer({ action, isMenuItem = false, externalOnClick }) {
6883
6887
  // Finally, wrap with Link if `to` is provided
6884
6888
  return to !== undefined && to !== "" ? (jsxRuntime.jsx(reactRouter.Link, { target: target, to: to, children: wrappedWithTooltip })) : (wrappedWithTooltip);
6885
6889
  }
6890
+
6886
6891
  /**
6887
6892
  * The PageHeaderSecondaryActions component is used to render the secondary actions in the PageHeader component.
6888
6893
  *
@@ -6890,9 +6895,13 @@ function ActionRenderer({ action, isMenuItem = false, externalOnClick }) {
6890
6895
  * @param {Array<PageHeaderSecondaryActionType>} props.actions - The secondary actions to render
6891
6896
  * @param {boolean} [props.hasPrimaryAction] - Whether there is a primary action present
6892
6897
  * @param {boolean} [props.groupActions] - Whether to group actions in a More Menu regardless of action count
6898
+ * @param {string} [props.className] - A custom class name for the action container
6899
+ * @param {string} [props."data-testid"] - An ID used to locate the container in tests
6900
+ * @param {object} [props.style] - Inline styles for the action container
6901
+ * @param {unknown} [props.ref] - Ref for the action container element
6893
6902
  * @returns {ReactElement | null} PageHeaderSecondaryActions component
6894
6903
  */
6895
- const PageHeaderSecondaryActions = ({ actions, hasPrimaryAction = false, groupActions = false, }) => {
6904
+ const PageHeaderSecondaryActions = ({ actions, hasPrimaryAction = false, groupActions = false, className, "data-testid": dataTestId, style, ref, }) => {
6896
6905
  const enabledActions = react.useMemo(() => actions.filter(action => action.hidden === false || action.hidden === undefined), [actions]);
6897
6906
  // If there are no enabled actions, don't render anything
6898
6907
  if (enabledActions.length === 0) {
@@ -6909,10 +6918,10 @@ const PageHeaderSecondaryActions = ({ actions, hasPrimaryAction = false, groupAc
6909
6918
  return [danger, [...others, action]];
6910
6919
  }
6911
6920
  }, [[], []]);
6912
- return (jsxRuntime.jsx(MoreMenu, { "data-testid": "secondary-actions-more-menu", iconButtonProps: { size: "small", variant: "secondary" }, children: close => (jsxRuntime.jsxs(MenuList, { className: "min-w-[160px]", children: [otherActions.map((action, index) => (jsxRuntime.jsx(ActionRenderer, { action: action, externalOnClick: close, isMenuItem: true }, `${action.actionText}-${index}`))), dangerActions.length > 0 ? jsxRuntime.jsx(MenuDivider, {}) : null, dangerActions.map((action, index) => (jsxRuntime.jsx(ActionRenderer, { action: action, externalOnClick: close, isMenuItem: true }, `${action.actionText}-${index}`)))] })) }));
6921
+ return (jsxRuntime.jsx("div", { className: className, "data-testid": dataTestId, ref: ref, style: style, children: jsxRuntime.jsx(MoreMenu, { "data-testid": "secondary-actions-more-menu", iconButtonProps: { size: "small", variant: "secondary" }, children: close => (jsxRuntime.jsxs(MenuList, { className: "min-w-[160px]", children: [otherActions.map((action, index) => (jsxRuntime.jsx(ActionRenderer, { action: action, externalOnClick: close, isMenuItem: true }, `${action.actionText}-${index}`))), dangerActions.length > 0 ? jsxRuntime.jsx(MenuDivider, {}) : null, dangerActions.map((action, index) => (jsxRuntime.jsx(ActionRenderer, { action: action, externalOnClick: close, isMenuItem: true }, `${action.actionText}-${index}`)))] })) }) }));
6913
6922
  }
6914
6923
  // Otherwise, render them inline as buttons
6915
- return (jsxRuntime.jsx("div", { className: "flex flex-row items-center gap-2", children: enabledActions
6924
+ return (jsxRuntime.jsx("div", { className: tailwindMerge.twMerge("flex flex-row items-center gap-2", className), "data-testid": dataTestId, ref: ref, style: style, children: enabledActions
6916
6925
  .toSorted((a, b) => {
6917
6926
  if (a.variant === "secondary" && b.variant === "secondary-danger") {
6918
6927
  return 1;
@@ -9900,6 +9909,10 @@ const cvaTab = cssClassVarianceUtilities.cvaMerge([
9900
9909
  * ### When not to use
9901
9910
  * Do not use Tab outside of a `TabList`/`Tabs` context. For standalone buttons, use `Button`.
9902
9911
  *
9912
+ * ### `data-testid` and `asChild`
9913
+ * `data-testid` is applied to the rendered element. When `asChild` is used and the child already
9914
+ * defines its own `data-testid`, the child's value wins so consumers can label the actual DOM node.
9915
+ *
9903
9916
  * @example Tab with icon and badge suffix
9904
9917
  * ```tsx
9905
9918
  * import { Tabs, TabList, Tab, TabContent, Badge } from "@trackunit/react-components";
@@ -9922,14 +9935,15 @@ const cvaTab = cssClassVarianceUtilities.cvaMerge([
9922
9935
  */
9923
9936
  const Tab = ({ value, isFullWidth = false, iconName = undefined, "data-testid": dataTestId, className, children, suffix, asChild = false, appendTabStylesToChildIfAsChild = true, ref, ...rest }) => {
9924
9937
  const renderContent = () => (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [iconName !== undefined ? jsxRuntime.jsx(Icon, { name: iconName, size: "small" }) : null, react.isValidElement(children) ? children.props.children : children, suffix] }));
9925
- const commonProps = {
9938
+ const sharedProps = {
9926
9939
  className: appendTabStylesToChildIfAsChild ? cvaTab({ className, isFullWidth }) : className,
9927
9940
  ...rest,
9928
9941
  };
9929
9942
  return (jsxRuntime.jsx(reactTabs.Trigger, { asChild: true, ref: ref, value: value, children: asChild && typeof children !== "string" ? (react.cloneElement(children, {
9930
- ...commonProps,
9943
+ ...sharedProps,
9944
+ "data-testid": children.props["data-testid"] ?? dataTestId,
9931
9945
  children: renderContent(),
9932
- })) : (jsxRuntime.jsx("button", { ...commonProps, "data-testid": dataTestId, children: renderContent() })) }));
9946
+ })) : (jsxRuntime.jsx("button", { ...sharedProps, "data-testid": dataTestId, children: renderContent() })) }));
9933
9947
  };
9934
9948
 
9935
9949
  /**
@@ -10373,13 +10387,13 @@ const getValueTextVariant = (size, sum, segments, total) => {
10373
10387
  * SegmentedValueBar displays multiple colored segments on a bar to visualize values relative to a total.
10374
10388
  * Supports optional tooltips per segment, showing value and optionally a label.
10375
10389
  */
10376
- const SegmentedValueBar = ({ segments, total, size = "small", showValue = false, displayValue, unit, valueColor, showTooltip = false, tooltipUnit, valueWidth, className, "data-testid": dataTestId, style, }) => {
10390
+ const SegmentedValueBar = ({ segments, total, size = "small", showValue = false, displayValue, unit, valueColor, showTooltip = false, tooltipUnit, valueWidth, className, "data-testid": dataTestId, ref, style, }) => {
10377
10391
  const computedSegments = computeSegments(segments, total);
10378
10392
  const sum = total > 0 ? computeSum(segments) : 0;
10379
10393
  const valueText = formatValue(displayValue ?? sum, unit);
10380
10394
  const canShowValue = showValue && size !== "extraSmall";
10381
10395
  const valueTextClassName = cvaSegmentedValueBarText({ size: getValueTextVariant(size, sum, segments, total) });
10382
- return (jsxRuntime.jsxs("span", { className: valueBarContainerClassName, "data-testid": dataTestId, style: style, children: [jsxRuntime.jsx("div", { "aria-label": valueText, className: cvaSegmentedValueBar({ className, size }), "data-testid": dataTestId ? `${dataTestId}-track` : undefined, children: computedSegments.map((segment, index) => {
10396
+ return (jsxRuntime.jsxs("span", { className: valueBarContainerClassName, "data-testid": dataTestId, ref: ref, style: style, children: [jsxRuntime.jsx("div", { "aria-label": valueText, className: cvaSegmentedValueBar({ className, size }), "data-testid": dataTestId ? `${dataTestId}-track` : undefined, children: computedSegments.map((segment, index) => {
10383
10397
  const tooltipLabel = segment.label
10384
10398
  ? `${segment.label}: ${formatValue(segment.value, tooltipUnit ?? unit)}`
10385
10399
  : formatValue(segment.value, tooltipUnit ?? unit);
@@ -12476,11 +12490,9 @@ const useWindowActivity = ({ onFocus, onBlur, skip = false } = { onBlur: undefin
12476
12490
  return react.useMemo(() => ({ focused }), [focused]);
12477
12491
  };
12478
12492
 
12479
- exports.ActionRenderer = ActionRenderer;
12480
12493
  exports.Alert = Alert;
12481
12494
  exports.Badge = Badge;
12482
12495
  exports.Breadcrumb = Breadcrumb;
12483
- exports.BreadcrumbContainer = BreadcrumbContainer;
12484
12496
  exports.Button = Button;
12485
12497
  exports.Card = Card;
12486
12498
  exports.CardBody = CardBody;
package/index.esm.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
+ import { twMerge } from 'tailwind-merge';
2
3
  import { objectKeys, uuidv4, parseTailwindArbitraryValue, objectEntries, nonNullable, objectValues, filterByMultiple } from '@trackunit/shared-utils';
3
4
  import { useRef, useMemo, useEffect, useState, useLayoutEffect, useCallback, createElement, createContext, useContext, isValidElement, cloneElement, Fragment as Fragment$1, memo, forwardRef, useId, useReducer, Children } from 'react';
4
5
  import { rentalStatusPalette, sitesPalette, utilizationPalette, activityPalette, criticalityPalette, generalPalette, intentPalette, themeScreenSizeAsNumber, themeContainerSize, color } from '@trackunit/ui-design-tokens';
@@ -13,7 +14,6 @@ import { Slot, Slottable } from '@radix-ui/react-slot';
13
14
  import { Link, useBlocker, useNavigate, useLocation, useRouter, useSearch } from '@tanstack/react-router';
14
15
  import { isEqual, omit } from 'es-toolkit';
15
16
  import { useFloating, offset, flip, shift, size, autoUpdate, useClick, useDismiss, useHover as useHover$1, safePolygon, useRole, useInteractions, FloatingPortal, useMergeRefs as useMergeRefs$1, FloatingFocusManager, arrow, useTransitionStatus, FloatingArrow } from '@floating-ui/react';
16
- import { twMerge } from 'tailwind-merge';
17
17
  import { useVirtualizer } from '@tanstack/react-virtual';
18
18
  import { HelmetProvider, Helmet } from 'react-helmet-async';
19
19
  import { Trigger, Content as Content$1, List as List$1, Root } from '@radix-ui/react-tabs';
@@ -357,7 +357,7 @@ const buildLibraryHref = (slug) => {
357
357
  * A component used to display the package name and version in the Storybook docs.
358
358
  * The package-name tag links to the matching section on the "Libraries" overview page.
359
359
  */
360
- const PackageNameStoryComponent = ({ packageJSON }) => {
360
+ const PackageNameStoryComponent = ({ packageJSON, className, "data-testid": dataTestId, style, ref, }) => {
361
361
  const name = packageJSON?.name;
362
362
  const slug = name ? toLibrarySlug(name) : undefined;
363
363
  const href = slug ? buildLibraryHref(slug) : undefined;
@@ -371,7 +371,7 @@ const PackageNameStoryComponent = ({ packageJSON }) => {
371
371
  if (top)
372
372
  top.location.href = href;
373
373
  };
374
- return (jsxs("div", { className: "flex gap-2", children: [href ? (jsx("a", { "aria-label": `Learn more about ${name}`, className: "no-underline", href: href, onClick: onClick, target: "_top", title: `Learn more about ${name}`, children: jsx(Tag, { color: "neutral", children: name }) })) : (jsx(Tag, { color: "neutral", children: name })), packageJSON?.version ? jsxs(Tag, { color: "neutral", children: ["v", packageJSON.version] }) : null] }));
374
+ return (jsxs("div", { className: twMerge("flex gap-2", className), "data-testid": dataTestId, ref: ref, style: style, children: [href ? (jsx("a", { "aria-label": `Learn more about ${name}`, className: "no-underline", href: href, onClick: onClick, target: "_top", title: `Learn more about ${name}`, children: jsx(Tag, { color: "neutral", children: name }) })) : (jsx(Tag, { color: "neutral", children: name })), packageJSON?.version ? jsxs(Tag, { color: "neutral", children: ["v", packageJSON.version] }) : null] }));
375
375
  };
376
376
 
377
377
  const docs = {
@@ -1346,6 +1346,19 @@ const Badge = ({ color = "primary", size = "default", compact = false, className
1346
1346
  return (jsx("span", { className: cvaBadge({ color, size, className, compact, isSingleChar }), "data-testid": dataTestId, ref: ref, style: style, children: compact ? null : displayedCount }));
1347
1347
  };
1348
1348
 
1349
+ const cvaBreadcrumb = cvaMerge(["my-4", "flex", "place-items-center"]);
1350
+ const cvaBreadcrumbItem = cvaMerge(["flex", "items-center"]);
1351
+ const cvaBreadcrumbForLargeScreen = cvaMerge(["flex", "flex-nowrap", "items-center"]);
1352
+ const cvaBreadcrumbForMediumScreen = cvaMerge(["flex", "items-center"], {
1353
+ variants: {
1354
+ expanded: {
1355
+ true: "flex-wrap",
1356
+ false: "flex-nowrap",
1357
+ },
1358
+ },
1359
+ defaultVariants: { expanded: false },
1360
+ });
1361
+
1349
1362
  /**
1350
1363
  * Maps size keys to their corresponding state property names.
1351
1364
  */
@@ -1443,19 +1456,6 @@ const useViewportBreakpoints = (options = {}) => {
1443
1456
  return viewportSize;
1444
1457
  };
1445
1458
 
1446
- const cvaBreadcrumb = cvaMerge(["my-4", "flex", "place-items-center"]);
1447
- const cvaBreadcrumbItem = cvaMerge(["flex", "items-center"]);
1448
- const cvaBreadcrumbForLargeScreen = cvaMerge(["flex", "flex-nowrap", "items-center"]);
1449
- const cvaBreadcrumbForMediumScreen = cvaMerge(["flex", "items-center"], {
1450
- variants: {
1451
- expanded: {
1452
- true: "flex-wrap",
1453
- false: "flex-nowrap",
1454
- },
1455
- },
1456
- defaultVariants: { expanded: false },
1457
- });
1458
-
1459
1459
  /**
1460
1460
  * BreadcrumbItem is a helper component that renders the individual breadcrumb item.
1461
1461
  */
@@ -1498,6 +1498,23 @@ const BreadcrumbForSmallScreen = ({ "data-testid": dataTestId, breadcrumbItems,
1498
1498
  return (jsx("div", { className: className, "data-testid": dataTestId, style: style, children: lastBreadcrumbItem.map((item, index) => (jsx(BreadcrumbItem, { "data-testid": `breadcrumbItem-${dataTestId}`, item: item }, `breadcrumbItem-${index}`))) }));
1499
1499
  };
1500
1500
 
1501
+ /**
1502
+ * BreadcrumbContainer is a helper component that renders the breadcrumb items based on the screen size.
1503
+ *
1504
+ * @param {BreadcrumbContainerProps} props - The props for the BreadcrumbContainer component
1505
+ */
1506
+ const BreadcrumbContainer = ({ className, "data-testid": dataTestId, breadcrumbItems, style, ref, }) => {
1507
+ const { isMd: isMediumScreen, isXs: isSmallScreen } = useViewportBreakpoints();
1508
+ const sharedContainerProps = { className, "data-testid": dataTestId, ref, style };
1509
+ if (isMediumScreen) {
1510
+ return (jsx("div", { ...sharedContainerProps, children: jsx(BreadcrumbForMediumScreen, { breadcrumbItems: breadcrumbItems, "data-testid": `mediumScreen-${dataTestId}` }) }));
1511
+ }
1512
+ if (isSmallScreen) {
1513
+ return (jsx("div", { ...sharedContainerProps, children: jsx(BreadcrumbForSmallScreen, { breadcrumbItems: breadcrumbItems, "data-testid": `smallScreen-${dataTestId}` }) }));
1514
+ }
1515
+ return (jsx("div", { ...sharedContainerProps, children: jsx(BreadcrumbForLargeScreen, { breadcrumbItems: breadcrumbItems, "data-testid": `largeScreen-${dataTestId}` }) }));
1516
+ };
1517
+
1501
1518
  /**
1502
1519
  * useBreadcrumbItemsToRender is a custom hook that takes an array of BreadcrumbItemProps and returns an array of React.ReactElement
1503
1520
  */
@@ -1551,21 +1568,6 @@ const Breadcrumb = ({ className, "data-testid": dataTestId, breadcrumbItems, onC
1551
1568
  const breadCrumbItemsToJSX = useBreadcrumbItemsToRender(breadcrumbItems);
1552
1569
  return (jsxs("div", { className: cvaBreadcrumb({ className }), "data-testid": dataTestId, ref: ref, style: style, children: [jsx(IconButton, { "data-testid": `backButton-${dataTestId}`, icon: jsx(Icon, { name: "ArrowLeft", size: "small" }), onClick: onClickBack, size: "small", variant: "ghost-neutral" }), jsx("div", { children: jsx(BreadcrumbContainer, { breadcrumbItems: breadCrumbItemsToJSX, "data-testid": dataTestId }) })] }));
1553
1570
  };
1554
- /**
1555
- * BreadcrumbContainer is a helper component that renders the breadcrumb items based on the screen size.
1556
- *
1557
- * @param {BreadcrumbContainerProps} props - The props for the BreadcrumbContainer component
1558
- */
1559
- const BreadcrumbContainer = ({ "data-testid": dataTestId, breadcrumbItems, }) => {
1560
- const { isMd: isMediumScreen, isXs: isSmallScreen } = useViewportBreakpoints();
1561
- if (isMediumScreen) {
1562
- return jsx(BreadcrumbForMediumScreen, { breadcrumbItems: breadcrumbItems, "data-testid": `mediumScreen-${dataTestId}` });
1563
- }
1564
- if (isSmallScreen) {
1565
- return jsx(BreadcrumbForSmallScreen, { breadcrumbItems: breadcrumbItems, "data-testid": `smallScreen-${dataTestId}` });
1566
- }
1567
- return jsx(BreadcrumbForLargeScreen, { breadcrumbItems: breadcrumbItems, "data-testid": `largeScreen-${dataTestId}` });
1568
- };
1569
1571
 
1570
1572
  /**
1571
1573
  * StarButton renders a clickable star icon for toggling favorite/starred state.
@@ -1590,8 +1592,8 @@ const BreadcrumbContainer = ({ "data-testid": dataTestId, breadcrumbItems, }) =>
1590
1592
  * @param {StarButtonProps} props - The props for the StarButton component
1591
1593
  * @returns {ReactElement} StarButton component
1592
1594
  */
1593
- const StarButton = ({ starred, onClick, ref }) => {
1594
- return (jsx("div", { "data-test-id": "starred-filter", onClick: onClick, ref: ref, children: jsx(Icon, { color: starred ? "primary" : "neutral", name: "Star", size: "medium" }) }));
1595
+ const StarButton = ({ starred, onClick, className, "data-testid": dataTestId, style, ref, }) => {
1596
+ return (jsx("div", { className: className, "data-testid": dataTestId ?? "starred-filter", onClick: onClick, ref: ref, style: style, children: jsx(Icon, { color: starred ? "primary" : "neutral", name: "Star", size: "medium" }) }));
1595
1597
  };
1596
1598
 
1597
1599
  const cvaCard = cvaMerge([
@@ -3683,9 +3685,10 @@ function createGrid() {
3683
3685
  * }
3684
3686
  * ```
3685
3687
  */
3686
- function GridAreas({ slots, css, containerProps, validationRef, className, style, children, asChild = false, }) {
3688
+ function GridAreas({ slots, css, containerProps, validationRef, className, "data-testid": dataTestId, ref, style, children, asChild = false, }) {
3687
3689
  const Comp = asChild ? Slot : "div";
3688
- return (jsxs(Fragment, { children: [jsx("style", { children: css }), jsx(Comp, { ref: validationRef, ...containerProps, className: twMerge("@container grid", className), style: style, children: children(slots) })] }));
3690
+ const mergedRef = useMergeRefs([validationRef, ref]);
3691
+ return (jsxs(Fragment, { children: [jsx("style", { children: css }), jsx(Comp, { ...containerProps, className: twMerge("@container grid", className), "data-testid": dataTestId, ref: mergedRef, style: style, children: children(slots) })] }));
3689
3692
  }
3690
3693
 
3691
3694
  /**
@@ -5664,15 +5667,16 @@ const ListLoadingIndicator = ({ type, hasThumbnail, thumbnailShape, hasDescripti
5664
5667
  * );
5665
5668
  * ```
5666
5669
  */
5667
- const List = ({ children, className, "data-testid": dataTestId, style,
5670
+ const List = ({ children, className, "data-testid": dataTestId, style, ref,
5668
5671
  // UseListResult properties
5669
5672
  containerRef, listRef, rows, getListItemProps, header, loadingIndicator, shouldShowLoaderAtIndex, count, isScrolling, separator, topSeparatorOnScroll, isAtTop, contentFillsContainer,
5670
5673
  // Unused but part of UseListResult interface (can be used from parent)
5671
5674
  scrollOffset: _scrollOffset, getTotalSize: _getTotalSize, getVirtualItems: _getVirtualItems, scrollToOffset: _scrollToOffset, scrollToIndex: _scrollToIndex, measure: _measure, }) => {
5675
+ const mergedContainerRef = useMergeRefs([containerRef, ref]);
5672
5676
  return (jsx("div", { className: cvaListContainer({
5673
5677
  withTopSeparator: topSeparatorOnScroll && !isAtTop,
5674
5678
  className,
5675
- }), "data-is-scrolling": isScrolling, "data-testid": dataTestId, ref: containerRef, style: style, children: jsx("ul", { className: cvaList(), ref: listRef, children: rows.map(row => {
5679
+ }), "data-is-scrolling": isScrolling, "data-testid": dataTestId, ref: mergedContainerRef, style: style, children: jsx("ul", { className: cvaList(), ref: listRef, children: rows.map(row => {
5676
5680
  // Generate list item props with clean separator styling
5677
5681
  const listItemProps = getListItemProps(row, {
5678
5682
  className: cvaListItem$1({
@@ -6214,8 +6218,8 @@ const cvaMenuListItem = cvaMerge("max-w-full");
6214
6218
  * ```
6215
6219
  * @returns {ReactElement} MenuDivider component
6216
6220
  */
6217
- const MenuDivider = ({ style }) => {
6218
- return jsx("div", { className: cvaMenuListDivider(), "data-testid": "menu-divider", style: style });
6221
+ const MenuDivider = ({ className, style, "data-testid": dataTestId, ref }) => {
6222
+ return (jsx("div", { className: cvaMenuListDivider({ className }), "data-testid": dataTestId ?? "menu-divider", ref: ref, style: style }));
6219
6223
  };
6220
6224
 
6221
6225
  /**
@@ -6881,6 +6885,7 @@ function ActionRenderer({ action, isMenuItem = false, externalOnClick }) {
6881
6885
  // Finally, wrap with Link if `to` is provided
6882
6886
  return to !== undefined && to !== "" ? (jsx(Link, { target: target, to: to, children: wrappedWithTooltip })) : (wrappedWithTooltip);
6883
6887
  }
6888
+
6884
6889
  /**
6885
6890
  * The PageHeaderSecondaryActions component is used to render the secondary actions in the PageHeader component.
6886
6891
  *
@@ -6888,9 +6893,13 @@ function ActionRenderer({ action, isMenuItem = false, externalOnClick }) {
6888
6893
  * @param {Array<PageHeaderSecondaryActionType>} props.actions - The secondary actions to render
6889
6894
  * @param {boolean} [props.hasPrimaryAction] - Whether there is a primary action present
6890
6895
  * @param {boolean} [props.groupActions] - Whether to group actions in a More Menu regardless of action count
6896
+ * @param {string} [props.className] - A custom class name for the action container
6897
+ * @param {string} [props."data-testid"] - An ID used to locate the container in tests
6898
+ * @param {object} [props.style] - Inline styles for the action container
6899
+ * @param {unknown} [props.ref] - Ref for the action container element
6891
6900
  * @returns {ReactElement | null} PageHeaderSecondaryActions component
6892
6901
  */
6893
- const PageHeaderSecondaryActions = ({ actions, hasPrimaryAction = false, groupActions = false, }) => {
6902
+ const PageHeaderSecondaryActions = ({ actions, hasPrimaryAction = false, groupActions = false, className, "data-testid": dataTestId, style, ref, }) => {
6894
6903
  const enabledActions = useMemo(() => actions.filter(action => action.hidden === false || action.hidden === undefined), [actions]);
6895
6904
  // If there are no enabled actions, don't render anything
6896
6905
  if (enabledActions.length === 0) {
@@ -6907,10 +6916,10 @@ const PageHeaderSecondaryActions = ({ actions, hasPrimaryAction = false, groupAc
6907
6916
  return [danger, [...others, action]];
6908
6917
  }
6909
6918
  }, [[], []]);
6910
- return (jsx(MoreMenu, { "data-testid": "secondary-actions-more-menu", iconButtonProps: { size: "small", variant: "secondary" }, children: close => (jsxs(MenuList, { className: "min-w-[160px]", children: [otherActions.map((action, index) => (jsx(ActionRenderer, { action: action, externalOnClick: close, isMenuItem: true }, `${action.actionText}-${index}`))), dangerActions.length > 0 ? jsx(MenuDivider, {}) : null, dangerActions.map((action, index) => (jsx(ActionRenderer, { action: action, externalOnClick: close, isMenuItem: true }, `${action.actionText}-${index}`)))] })) }));
6919
+ return (jsx("div", { className: className, "data-testid": dataTestId, ref: ref, style: style, children: jsx(MoreMenu, { "data-testid": "secondary-actions-more-menu", iconButtonProps: { size: "small", variant: "secondary" }, children: close => (jsxs(MenuList, { className: "min-w-[160px]", children: [otherActions.map((action, index) => (jsx(ActionRenderer, { action: action, externalOnClick: close, isMenuItem: true }, `${action.actionText}-${index}`))), dangerActions.length > 0 ? jsx(MenuDivider, {}) : null, dangerActions.map((action, index) => (jsx(ActionRenderer, { action: action, externalOnClick: close, isMenuItem: true }, `${action.actionText}-${index}`)))] })) }) }));
6911
6920
  }
6912
6921
  // Otherwise, render them inline as buttons
6913
- return (jsx("div", { className: "flex flex-row items-center gap-2", children: enabledActions
6922
+ return (jsx("div", { className: twMerge("flex flex-row items-center gap-2", className), "data-testid": dataTestId, ref: ref, style: style, children: enabledActions
6914
6923
  .toSorted((a, b) => {
6915
6924
  if (a.variant === "secondary" && b.variant === "secondary-danger") {
6916
6925
  return 1;
@@ -9898,6 +9907,10 @@ const cvaTab = cvaMerge([
9898
9907
  * ### When not to use
9899
9908
  * Do not use Tab outside of a `TabList`/`Tabs` context. For standalone buttons, use `Button`.
9900
9909
  *
9910
+ * ### `data-testid` and `asChild`
9911
+ * `data-testid` is applied to the rendered element. When `asChild` is used and the child already
9912
+ * defines its own `data-testid`, the child's value wins so consumers can label the actual DOM node.
9913
+ *
9901
9914
  * @example Tab with icon and badge suffix
9902
9915
  * ```tsx
9903
9916
  * import { Tabs, TabList, Tab, TabContent, Badge } from "@trackunit/react-components";
@@ -9920,14 +9933,15 @@ const cvaTab = cvaMerge([
9920
9933
  */
9921
9934
  const Tab = ({ value, isFullWidth = false, iconName = undefined, "data-testid": dataTestId, className, children, suffix, asChild = false, appendTabStylesToChildIfAsChild = true, ref, ...rest }) => {
9922
9935
  const renderContent = () => (jsxs(Fragment, { children: [iconName !== undefined ? jsx(Icon, { name: iconName, size: "small" }) : null, isValidElement(children) ? children.props.children : children, suffix] }));
9923
- const commonProps = {
9936
+ const sharedProps = {
9924
9937
  className: appendTabStylesToChildIfAsChild ? cvaTab({ className, isFullWidth }) : className,
9925
9938
  ...rest,
9926
9939
  };
9927
9940
  return (jsx(Trigger, { asChild: true, ref: ref, value: value, children: asChild && typeof children !== "string" ? (cloneElement(children, {
9928
- ...commonProps,
9941
+ ...sharedProps,
9942
+ "data-testid": children.props["data-testid"] ?? dataTestId,
9929
9943
  children: renderContent(),
9930
- })) : (jsx("button", { ...commonProps, "data-testid": dataTestId, children: renderContent() })) }));
9944
+ })) : (jsx("button", { ...sharedProps, "data-testid": dataTestId, children: renderContent() })) }));
9931
9945
  };
9932
9946
 
9933
9947
  /**
@@ -10371,13 +10385,13 @@ const getValueTextVariant = (size, sum, segments, total) => {
10371
10385
  * SegmentedValueBar displays multiple colored segments on a bar to visualize values relative to a total.
10372
10386
  * Supports optional tooltips per segment, showing value and optionally a label.
10373
10387
  */
10374
- const SegmentedValueBar = ({ segments, total, size = "small", showValue = false, displayValue, unit, valueColor, showTooltip = false, tooltipUnit, valueWidth, className, "data-testid": dataTestId, style, }) => {
10388
+ const SegmentedValueBar = ({ segments, total, size = "small", showValue = false, displayValue, unit, valueColor, showTooltip = false, tooltipUnit, valueWidth, className, "data-testid": dataTestId, ref, style, }) => {
10375
10389
  const computedSegments = computeSegments(segments, total);
10376
10390
  const sum = total > 0 ? computeSum(segments) : 0;
10377
10391
  const valueText = formatValue(displayValue ?? sum, unit);
10378
10392
  const canShowValue = showValue && size !== "extraSmall";
10379
10393
  const valueTextClassName = cvaSegmentedValueBarText({ size: getValueTextVariant(size, sum, segments, total) });
10380
- return (jsxs("span", { className: valueBarContainerClassName, "data-testid": dataTestId, style: style, children: [jsx("div", { "aria-label": valueText, className: cvaSegmentedValueBar({ className, size }), "data-testid": dataTestId ? `${dataTestId}-track` : undefined, children: computedSegments.map((segment, index) => {
10394
+ return (jsxs("span", { className: valueBarContainerClassName, "data-testid": dataTestId, ref: ref, style: style, children: [jsx("div", { "aria-label": valueText, className: cvaSegmentedValueBar({ className, size }), "data-testid": dataTestId ? `${dataTestId}-track` : undefined, children: computedSegments.map((segment, index) => {
10381
10395
  const tooltipLabel = segment.label
10382
10396
  ? `${segment.label}: ${formatValue(segment.value, tooltipUnit ?? unit)}`
10383
10397
  : formatValue(segment.value, tooltipUnit ?? unit);
@@ -12474,4 +12488,4 @@ const useWindowActivity = ({ onFocus, onBlur, skip = false } = { onBlur: undefin
12474
12488
  return useMemo(() => ({ focused }), [focused]);
12475
12489
  };
12476
12490
 
12477
- export { ActionRenderer, Alert, Badge, Breadcrumb, BreadcrumbContainer, Button, Card, CardBody, CardFooter, CardHeader, Collapse, CompletionStatusIndicator, CopyableText, DEFAULT_SKELETON_PREFERENCE_CARD_PROPS, DetailsList, EmptyState, EmptyValue, ExternalLink, GridAreas, Heading, Highlight, HorizontalOverflowScroller, Icon, IconButton, Indicator, KPI, KPICard, KPICardSkeleton, KPISkeleton, List, ListItem, MAX_URL_LENGTH, MenuDivider, MenuItem, MenuList, MoreMenu, Notice, PackageNameStoryComponent, Page, PageContent, PageHeader, PageHeaderKpiMetrics, PageHeaderSecondaryActions, PageHeaderTitle, Pagination, Polygon, Popover, PopoverContent, PopoverTitle, PopoverTrigger, Portal, PreferenceCard, PreferenceCardSkeleton, Prompt, ROLE_CARD, SHEET_TRANSITION_DURATION, SHEET_TRANSITION_DURATION_MS, SHEET_TRANSITION_EASING, SectionHeader, SegmentedValueBar, Sheet, Sidebar, SkeletonBlock, SkeletonLabel, SkeletonLines, Spacer, Spinner, StarButton, Tab, TabContent, TabList, Tabs, Tag, Text, ToggleGroup, Tooltip, TrendIndicator, TrendIndicators, ValueBar, ZStack, createGrid, cvaButton, cvaButtonPrefixSuffix, cvaButtonSpinner, cvaButtonSpinnerContainer, cvaClickable, cvaContainerStyles, cvaContentContainer, cvaContentWrapper, cvaDescriptionCard, cvaIconBackground, cvaIconButton, cvaImgStyles, cvaIndicator, cvaIndicatorIcon, cvaIndicatorIconBackground, cvaIndicatorLabel, cvaIndicatorPing, cvaInputContainer, cvaInteractableItem, cvaList, cvaListContainer, cvaListItem$1 as cvaListItem, cvaMenu, cvaMenuItem, cvaMenuItemLabel, cvaMenuItemPrefix, cvaMenuItemStyle, cvaMenuItemSuffix, cvaMenuList, cvaMenuListDivider, cvaMenuListItem, cvaMenuListMultiSelect, cvaPageHeader, cvaPageHeaderContainer, cvaPageHeaderHeading, cvaPreferenceCard, cvaTitleCard, cvaToggleGroup, cvaToggleGroupWithSlidingBackground, cvaToggleItem, cvaToggleItemContent, cvaToggleItemText, cvaZStackContainer, cvaZStackItem, defaultPageSize, docs, getDevicePixelRatio, getValueBarColorByValue, iconColorNames, iconPalette, noPagination, preferenceCardGrid, storageSerializer, useBidirectionalScroll, useClickOutside, useContainerBreakpoints, useContinuousTimeout, useCopyToClipboard, useCursorUrlSync, useCustomEncoding, useDebounce, useDevicePixelRatio, useElevatedReducer, useElevatedState, useGridAreas, useHover, useInfiniteScroll, useIsFirstRender, useIsFullscreen, useIsTextTruncated, useKeyboardShortcut, useList, useListItemHeight, useLocalStorage, useLocalStorageReducer, useMeasure, useMergeRefs, useModifierKey, useOverflowBorder, useOverflowItems, usePersistedState, usePopoverContext, usePrevious, usePrompt, useRandomCSSLengths, useRelayPagination, useResize, useScrollBlock, useScrollDetection, useSearchParamSync, useSelfUpdatingRef, useSessionStorage, useSessionStorageReducer, useSheet, useSheetSnap, useStorageKey, useTextSearch, useTimeout, useViewportBreakpoints, useWatch, useWindowActivity };
12491
+ export { Alert, Badge, Breadcrumb, Button, Card, CardBody, CardFooter, CardHeader, Collapse, CompletionStatusIndicator, CopyableText, DEFAULT_SKELETON_PREFERENCE_CARD_PROPS, DetailsList, EmptyState, EmptyValue, ExternalLink, GridAreas, Heading, Highlight, HorizontalOverflowScroller, Icon, IconButton, Indicator, KPI, KPICard, KPICardSkeleton, KPISkeleton, List, ListItem, MAX_URL_LENGTH, MenuDivider, MenuItem, MenuList, MoreMenu, Notice, PackageNameStoryComponent, Page, PageContent, PageHeader, PageHeaderKpiMetrics, PageHeaderSecondaryActions, PageHeaderTitle, Pagination, Polygon, Popover, PopoverContent, PopoverTitle, PopoverTrigger, Portal, PreferenceCard, PreferenceCardSkeleton, Prompt, ROLE_CARD, SHEET_TRANSITION_DURATION, SHEET_TRANSITION_DURATION_MS, SHEET_TRANSITION_EASING, SectionHeader, SegmentedValueBar, Sheet, Sidebar, SkeletonBlock, SkeletonLabel, SkeletonLines, Spacer, Spinner, StarButton, Tab, TabContent, TabList, Tabs, Tag, Text, ToggleGroup, Tooltip, TrendIndicator, TrendIndicators, ValueBar, ZStack, createGrid, cvaButton, cvaButtonPrefixSuffix, cvaButtonSpinner, cvaButtonSpinnerContainer, cvaClickable, cvaContainerStyles, cvaContentContainer, cvaContentWrapper, cvaDescriptionCard, cvaIconBackground, cvaIconButton, cvaImgStyles, cvaIndicator, cvaIndicatorIcon, cvaIndicatorIconBackground, cvaIndicatorLabel, cvaIndicatorPing, cvaInputContainer, cvaInteractableItem, cvaList, cvaListContainer, cvaListItem$1 as cvaListItem, cvaMenu, cvaMenuItem, cvaMenuItemLabel, cvaMenuItemPrefix, cvaMenuItemStyle, cvaMenuItemSuffix, cvaMenuList, cvaMenuListDivider, cvaMenuListItem, cvaMenuListMultiSelect, cvaPageHeader, cvaPageHeaderContainer, cvaPageHeaderHeading, cvaPreferenceCard, cvaTitleCard, cvaToggleGroup, cvaToggleGroupWithSlidingBackground, cvaToggleItem, cvaToggleItemContent, cvaToggleItemText, cvaZStackContainer, cvaZStackItem, defaultPageSize, docs, getDevicePixelRatio, getValueBarColorByValue, iconColorNames, iconPalette, noPagination, preferenceCardGrid, storageSerializer, useBidirectionalScroll, useClickOutside, useContainerBreakpoints, useContinuousTimeout, useCopyToClipboard, useCursorUrlSync, useCustomEncoding, useDebounce, useDevicePixelRatio, useElevatedReducer, useElevatedState, useGridAreas, useHover, useInfiniteScroll, useIsFirstRender, useIsFullscreen, useIsTextTruncated, useKeyboardShortcut, useList, useListItemHeight, useLocalStorage, useLocalStorageReducer, useMeasure, useMergeRefs, useModifierKey, useOverflowBorder, useOverflowItems, usePersistedState, usePopoverContext, usePrevious, usePrompt, useRandomCSSLengths, useRelayPagination, useResize, useScrollBlock, useScrollDetection, useSearchParamSync, useSelfUpdatingRef, useSessionStorage, useSessionStorageReducer, useSheet, useSheetSnap, useStorageKey, useTextSearch, useTimeout, useViewportBreakpoints, useWatch, useWindowActivity };
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "@trackunit/react-components",
3
- "version": "1.22.27",
3
+ "version": "1.23.2",
4
4
  "repository": "https://github.com/Trackunit/manager",
5
5
  "license": "SEE LICENSE IN LICENSE.txt",
6
+ "migrations": "./migrations.json",
6
7
  "engines": {
7
8
  "node": ">=24.x"
8
9
  },
@@ -13,10 +14,10 @@
13
14
  "@floating-ui/react": "^0.26.25",
14
15
  "string-ts": "^2.0.0",
15
16
  "tailwind-merge": "^2.0.0",
16
- "@trackunit/ui-design-tokens": "1.11.115",
17
- "@trackunit/css-class-variance-utilities": "1.11.118",
18
- "@trackunit/shared-utils": "1.13.118",
19
- "@trackunit/ui-icons": "1.11.114",
17
+ "@trackunit/ui-design-tokens": "1.12.1",
18
+ "@trackunit/css-class-variance-utilities": "1.12.1",
19
+ "@trackunit/shared-utils": "1.14.1",
20
+ "@trackunit/ui-icons": "1.12.2",
20
21
  "es-toolkit": "^1.39.10",
21
22
  "@tanstack/react-virtual": "3.13.12",
22
23
  "dequal": "^2.0.3",
@@ -1,5 +1,8 @@
1
1
  import { ReactElement } from "react";
2
- interface PackageNameStoryComponentProps {
2
+ import { CommonProps } from "./CommonProps";
3
+ import { Refable } from "./Refable";
4
+ import type { Styleable } from "./Styleable";
5
+ interface PackageNameStoryComponentProps extends CommonProps, Styleable, Refable<HTMLDivElement> {
3
6
  packageJSON?: {
4
7
  name?: string;
5
8
  version?: string;
@@ -9,5 +12,5 @@ interface PackageNameStoryComponentProps {
9
12
  * A component used to display the package name and version in the Storybook docs.
10
13
  * The package-name tag links to the matching section on the "Libraries" overview page.
11
14
  */
12
- export declare const PackageNameStoryComponent: ({ packageJSON }: PackageNameStoryComponentProps) => ReactElement;
15
+ export declare const PackageNameStoryComponent: ({ packageJSON, className, "data-testid": dataTestId, style, ref, }: PackageNameStoryComponentProps) => ReactElement;
13
16
  export {};
@@ -1,5 +1,5 @@
1
1
  import { ReactElement } from "react";
2
- import { BreadcrumbContainerProps, BreadcrumbProps } from "./utils/types";
2
+ import { BreadcrumbProps } from "./utils/types";
3
3
  /**
4
4
  * The breadcrumb component shows a user's location in a website or application. Breadcrumbs are particularly useful when a large amount of content is organized in a hierarchical manner. They streamline navigation, minimize the steps required to revisit previous pages, and offer contextual insights to the users.
5
5
  *
@@ -37,9 +37,3 @@ import { BreadcrumbContainerProps, BreadcrumbProps } from "./utils/types";
37
37
  * @returns {ReactElement} Breadcrumb component
38
38
  */
39
39
  export declare const Breadcrumb: ({ className, "data-testid": dataTestId, breadcrumbItems, onClickBack, style, ref, }: BreadcrumbProps) => ReactElement;
40
- /**
41
- * BreadcrumbContainer is a helper component that renders the breadcrumb items based on the screen size.
42
- *
43
- * @param {BreadcrumbContainerProps} props - The props for the BreadcrumbContainer component
44
- */
45
- export declare const BreadcrumbContainer: ({ "data-testid": dataTestId, breadcrumbItems, }: BreadcrumbContainerProps) => ReactElement;
@@ -0,0 +1,8 @@
1
+ import { ReactElement } from "react";
2
+ import { BreadcrumbContainerProps } from "./utils/types";
3
+ /**
4
+ * BreadcrumbContainer is a helper component that renders the breadcrumb items based on the screen size.
5
+ *
6
+ * @param {BreadcrumbContainerProps} props - The props for the BreadcrumbContainer component
7
+ */
8
+ export declare const BreadcrumbContainer: ({ className, "data-testid": dataTestId, breadcrumbItems, style, ref, }: BreadcrumbContainerProps) => ReactElement;
@@ -1,7 +1,7 @@
1
1
  import { MouseEventHandler, ReactElement } from "react";
2
2
  import { CommonProps } from "../../../common/CommonProps";
3
- import type { Styleable } from "../../../common/Styleable";
4
3
  import { Refable } from "../../../common/Refable";
4
+ import type { Styleable } from "../../../common/Styleable";
5
5
  export interface BreadcrumbItemProps {
6
6
  /** The display text for this breadcrumb item. */
7
7
  label: string;
@@ -19,7 +19,7 @@ export interface BreadcrumbProps extends CommonProps, Styleable, Refable<HTMLDiv
19
19
  */
20
20
  onClickBack: MouseEventHandler<HTMLButtonElement>;
21
21
  }
22
- export interface BreadcrumbContainerProps extends CommonProps, Styleable {
22
+ export interface BreadcrumbContainerProps extends CommonProps, Styleable, Refable<HTMLDivElement> {
23
23
  breadcrumbItems: Array<ReactElement<BreadcrumbItemProps>>;
24
24
  }
25
25
  export interface BreadcrumbItemRenderProps extends CommonProps, Styleable {
@@ -1,4 +1,6 @@
1
1
  import { ReactNode } from "react";
2
+ import { CommonProps } from "../../common/CommonProps";
3
+ import { Refable } from "../../common/Refable";
2
4
  import type { Styleable } from "../../common/Styleable";
3
5
  import { GridAreasResult, SlotProps } from "./types";
4
6
  /**
@@ -6,7 +8,7 @@ import { GridAreasResult, SlotProps } from "./types";
6
8
  *
7
9
  * Spread the result from useGridAreas() onto this component.
8
10
  */
9
- export type GridAreasProps<TAreaName extends string> = GridAreasResult<TAreaName> & Styleable & {
11
+ export type GridAreasProps<TAreaName extends string> = GridAreasResult<TAreaName> & CommonProps & Styleable & Refable<HTMLElement> & {
10
12
  /** Additional CSS classes to apply to the grid container */
11
13
  className?: string;
12
14
  /** Render prop that receives the slots object */
@@ -93,4 +95,4 @@ export type GridAreasProps<TAreaName extends string> = GridAreasResult<TAreaName
93
95
  * }
94
96
  * ```
95
97
  */
96
- export declare function GridAreas<TAreaName extends string>({ slots, css, containerProps, validationRef, className, style, children, asChild, }: GridAreasProps<TAreaName>): ReactNode;
98
+ export declare function GridAreas<TAreaName extends string>({ slots, css, containerProps, validationRef, className, "data-testid": dataTestId, ref, style, children, asChild, }: GridAreasProps<TAreaName>): ReactNode;
@@ -1,9 +1,10 @@
1
- import { ReactElement, ReactNode, Ref } from "react";
1
+ import { ReactElement, ReactNode } from "react";
2
2
  import { CommonProps } from "../../common/CommonProps";
3
- import type { Styleable } from "../../common/Styleable";
3
+ import { Refable } from "../../common/Refable";
4
4
  import { Size } from "../../common/Size";
5
+ import type { Styleable } from "../../common/Styleable";
5
6
  export type HighlightSize = Extract<Size, "small" | "medium">;
6
- export interface HighlightProps extends CommonProps, Styleable {
7
+ export interface HighlightProps extends CommonProps, Styleable, Refable<HTMLDivElement> {
7
8
  /**
8
9
  * The size of the highlight. Allowed values: "small" (text-xs), "medium" (text-sm).
9
10
  */
@@ -16,10 +17,6 @@ export interface HighlightProps extends CommonProps, Styleable {
16
17
  * The content of the highlight. Accepts any valid React children (text, numbers, elements).
17
18
  */
18
19
  children?: ReactNode;
19
- /**
20
- * A ref for the component
21
- */
22
- ref?: Ref<HTMLDivElement>;
23
20
  }
24
21
  /**
25
22
  * Highlight draws visual attention to data values that may require user action, monitoring, or investigation.
@@ -1,9 +1,10 @@
1
1
  import { ReactElement } from "react";
2
2
  import { CommonProps } from "../../common/CommonProps";
3
+ import { Refable } from "../../common/Refable";
3
4
  import type { Styleable } from "../../common/Styleable";
4
5
  import { UseListResult, VirtualizationListItemProps } from "./useList";
5
6
  export type { VirtualizationListItemProps } from "./useList";
6
- export interface ListProps<TItem = unknown> extends CommonProps, Styleable, UseListResult<TItem> {
7
+ export interface ListProps<TItem = unknown> extends CommonProps, Styleable, Refable<HTMLDivElement>, UseListResult<TItem> {
7
8
  /**
8
9
  * Function that renders each list item. Must spread `listItemProps` onto your element
9
10
  * and use the provided `key` prop for React's reconciliation.
@@ -92,4 +93,4 @@ export interface ListProps<TItem = unknown> extends CommonProps, Styleable, UseL
92
93
  * );
93
94
  * ```
94
95
  */
95
- export declare const List: <TItem = unknown>({ children, className, "data-testid": dataTestId, style, containerRef, listRef, rows, getListItemProps, header, loadingIndicator, shouldShowLoaderAtIndex, count, isScrolling, separator, topSeparatorOnScroll, isAtTop, contentFillsContainer, scrollOffset: _scrollOffset, getTotalSize: _getTotalSize, getVirtualItems: _getVirtualItems, scrollToOffset: _scrollToOffset, scrollToIndex: _scrollToIndex, measure: _measure, }: ListProps<TItem>) => ReactElement;
96
+ export declare const List: <TItem = unknown>({ children, className, "data-testid": dataTestId, style, ref, containerRef, listRef, rows, getListItemProps, header, loadingIndicator, shouldShowLoaderAtIndex, count, isScrolling, separator, topSeparatorOnScroll, isAtTop, contentFillsContainer, scrollOffset: _scrollOffset, getTotalSize: _getTotalSize, getVirtualItems: _getVirtualItems, scrollToOffset: _scrollToOffset, scrollToIndex: _scrollToIndex, measure: _measure, }: ListProps<TItem>) => ReactElement;
@@ -1,14 +1,13 @@
1
1
  import { VariantProps } from "@trackunit/css-class-variance-utilities";
2
2
  import type { MappedOmit } from "@trackunit/shared-utils";
3
3
  import { tailwindPalette, ThemeColors } from "@trackunit/ui-design-tokens";
4
- import { MouseEventHandler, ReactElement, ReactNode, Ref } from "react";
4
+ import { MouseEventHandler, ReactElement, ReactNode } from "react";
5
5
  import { CommonProps } from "../../common/CommonProps";
6
+ import { Refable } from "../../common/Refable";
6
7
  import type { Styleable } from "../../common/Styleable";
7
8
  import { cvaListItem } from "./ListItem.variants";
8
9
  type ThemeColorShades = `${keyof (typeof tailwindPalette)[keyof typeof tailwindPalette]}`;
9
- export type ListItemVirtualizationProps = {
10
- /** @internal */
11
- ref?: Ref<HTMLLIElement>;
10
+ export type ListItemVirtualizationProps = Refable<HTMLLIElement> & {
12
11
  /** @internal */
13
12
  tabIndex?: number;
14
13
  /** @internal */
@@ -16,7 +15,7 @@ export type ListItemVirtualizationProps = {
16
15
  /** @internal */
17
16
  className?: string;
18
17
  };
19
- export interface ListItemProps extends CommonProps, Styleable, ListItemVirtualizationProps, MappedOmit<VariantProps<typeof cvaListItem>, "className"> {
18
+ export interface ListItemProps extends CommonProps, Styleable, Refable<HTMLLIElement>, ListItemVirtualizationProps, MappedOmit<VariantProps<typeof cvaListItem>, "className"> {
20
19
  /**The main text line of the ListItem */
21
20
  title: string | ReactElement<CommonProps>;
22
21
  /**Optional description for the ListItem. Can be used for descriptions, metadata, or other important details. */
@@ -1,6 +1,8 @@
1
1
  import { ReactElement } from "react";
2
+ import { CommonProps } from "../../../common/CommonProps";
3
+ import { Refable } from "../../../common/Refable";
2
4
  import type { Styleable } from "../../../common/Styleable";
3
- export type MenuDividerProps = Styleable;
5
+ export type MenuDividerProps = CommonProps & Styleable & Refable<HTMLDivElement>;
4
6
  /**
5
7
  * MenuDivider renders a horizontal line to visually separate groups of items within a MenuList.
6
8
  *
@@ -25,4 +27,4 @@ export type MenuDividerProps = Styleable;
25
27
  * ```
26
28
  * @returns {ReactElement} MenuDivider component
27
29
  */
28
- export declare const MenuDivider: ({ style }: MenuDividerProps) => ReactElement;
30
+ export declare const MenuDivider: ({ className, style, "data-testid": dataTestId, ref }: MenuDividerProps) => ReactElement;
@@ -0,0 +1,25 @@
1
+ import { ReactElement } from "react";
2
+ import { CommonProps } from "../../../common/CommonProps";
3
+ import { Refable } from "../../../common/Refable";
4
+ import type { Styleable } from "../../../common/Styleable";
5
+ import { PageHeaderSecondaryActionType } from "../types";
6
+ export type ActionRendererProps = CommonProps & Styleable & Refable & {
7
+ action: PageHeaderSecondaryActionType;
8
+ /**
9
+ * Indicates if we should render a MenuItem or Button.
10
+ * If `isMenuItem` is true, we render `MenuItem`, otherwise we render `Button`.
11
+ */
12
+ isMenuItem?: boolean;
13
+ /**
14
+ * Because sometimes in a menu, you want to close the menu after clicking.
15
+ * If `externalOnClick` is provided, we'll call it after the action callback.
16
+ */
17
+ externalOnClick?: () => void;
18
+ };
19
+ /**
20
+ * The ActionRenderer component is used to render the action in the PageHeaderSecondaryActions component.
21
+ *
22
+ * @param {ActionRendererProps} props - The props for the ActionRenderer component
23
+ * @returns {ReactElement} ActionRenderer component
24
+ */
25
+ export declare function ActionRenderer({ action, isMenuItem, externalOnClick }: ActionRendererProps): ReactElement;
@@ -1,30 +1,13 @@
1
1
  import { ReactElement } from "react";
2
+ import { CommonProps } from "../../../common/CommonProps";
3
+ import { Refable } from "../../../common/Refable";
4
+ import type { Styleable } from "../../../common/Styleable";
2
5
  import { PageHeaderSecondaryActionType } from "../types";
3
- export type PageHeaderSecondaryActionsProps = {
6
+ export type PageHeaderSecondaryActionsProps = CommonProps & Styleable & Refable<HTMLDivElement> & {
4
7
  actions: Array<PageHeaderSecondaryActionType>;
5
8
  hasPrimaryAction?: boolean;
6
9
  groupActions?: boolean;
7
10
  };
8
- type ActionRendererProps = {
9
- action: PageHeaderSecondaryActionType;
10
- /**
11
- * Indicates if we should render a MenuItem or Button.
12
- * If `isMenuItem` is true, we render `MenuItem`, otherwise we render `Button`.
13
- */
14
- isMenuItem?: boolean;
15
- /**
16
- * Because sometimes in a menu, you want to close the menu after clicking.
17
- * If `externalOnClick` is provided, we’ll call it after the action callback.
18
- */
19
- externalOnClick?: () => void;
20
- };
21
- /**
22
- * The ActionRenderer component is used to render the action in the PageHeaderSecondaryActions component.
23
- *
24
- * @param {ActionRendererProps} props - The props for the ActionRenderer component
25
- * @returns {ReactElement} ActionRenderer component
26
- */
27
- export declare function ActionRenderer({ action, isMenuItem, externalOnClick }: ActionRendererProps): ReactElement;
28
11
  /**
29
12
  * The PageHeaderSecondaryActions component is used to render the secondary actions in the PageHeader component.
30
13
  *
@@ -32,7 +15,10 @@ export declare function ActionRenderer({ action, isMenuItem, externalOnClick }:
32
15
  * @param {Array<PageHeaderSecondaryActionType>} props.actions - The secondary actions to render
33
16
  * @param {boolean} [props.hasPrimaryAction] - Whether there is a primary action present
34
17
  * @param {boolean} [props.groupActions] - Whether to group actions in a More Menu regardless of action count
18
+ * @param {string} [props.className] - A custom class name for the action container
19
+ * @param {string} [props."data-testid"] - An ID used to locate the container in tests
20
+ * @param {object} [props.style] - Inline styles for the action container
21
+ * @param {unknown} [props.ref] - Ref for the action container element
35
22
  * @returns {ReactElement | null} PageHeaderSecondaryActions component
36
23
  */
37
- export declare const PageHeaderSecondaryActions: ({ actions, hasPrimaryAction, groupActions, }: PageHeaderSecondaryActionsProps) => ReactElement | null;
38
- export {};
24
+ export declare const PageHeaderSecondaryActions: ({ actions, hasPrimaryAction, groupActions, className, "data-testid": dataTestId, style, ref, }: PageHeaderSecondaryActionsProps) => ReactElement | null;
@@ -1,6 +1,9 @@
1
1
  import { FloatingPortalProps } from "@floating-ui/react";
2
2
  import { ReactElement } from "react";
3
- export type PortalProps = FloatingPortalProps;
3
+ import { CommonProps } from "../../common/CommonProps";
4
+ import { Refable } from "../../common/Refable";
5
+ import type { Styleable } from "../../common/Styleable";
6
+ export type PortalProps = FloatingPortalProps & CommonProps & Styleable & Refable<HTMLDivElement>;
4
7
  /**
5
8
  * Portal renders its children into a separate DOM node, outside the normal React tree hierarchy.
6
9
  * By default, content is portalled into a z-index-isolated `div#portal-container` in the document body.
@@ -38,4 +41,4 @@ export type PortalProps = FloatingPortalProps;
38
41
  * @param {PortalProps} props - The props for the Portal component
39
42
  * @returns {ReactElement} Portal component
40
43
  */
41
- export declare const Portal: (props: FloatingPortalProps) => ReactElement;
44
+ export declare const Portal: (props: PortalProps) => ReactElement;
@@ -1,5 +1,7 @@
1
1
  import type { UseFloatingReturn } from "@floating-ui/react";
2
- import type { ReactNode, Ref, RefObject } from "react";
2
+ import type { ReactNode, RefObject } from "react";
3
+ import type { CommonProps } from "../../common/CommonProps";
4
+ import type { Refable } from "../../common/Refable";
3
5
  import type { Styleable } from "../../common/Styleable";
4
6
  import type { UseOverlayDismissibleProps } from "../../overlay-dismissible/types";
5
7
  /**
@@ -146,7 +148,7 @@ export type UseSheetSnapReturn = {
146
148
  * actually reads. Consumers spread the hook return (`{...sheet}`) and
147
149
  * TypeScript allows extra properties on spread expressions.
148
150
  */
149
- export type SheetProps = Styleable & {
151
+ export type SheetProps = CommonProps & Styleable & Refable<HTMLDivElement> & {
150
152
  readonly isOpen: boolean;
151
153
  /** Unified sheet state from useSheetSnap. */
152
154
  readonly state: SheetState;
@@ -163,7 +165,6 @@ export type SheetProps = Styleable & {
163
165
  * Sheet skips its own focus trap and dismiss — the parent owns those.
164
166
  */
165
167
  readonly floatingUi?: SheetFloatingUiProps;
166
- readonly ref?: Ref<HTMLDivElement>;
167
168
  /** Horizontal positioning of the sheet. */
168
169
  readonly anchor?: SheetAnchor;
169
170
  /** Enable/disable drag-to-snap between levels. Default: true. */
@@ -197,10 +198,6 @@ export type SheetProps = Styleable & {
197
198
  * mode shows only `persistentContent`.
198
199
  */
199
200
  readonly persistentContent?: ReactNode;
200
- /** Custom class name. */
201
- readonly className?: string;
202
- /** Test ID for the sheet. */
203
- readonly "data-testid"?: string;
204
201
  /** Sheet content. */
205
202
  readonly children?: ReactNode;
206
203
  /** Called after the close CSS transition completes (transitionend on transform). */
@@ -1,20 +1,17 @@
1
1
  import { TabsTriggerProps } from "@radix-ui/react-tabs";
2
2
  import { IconName } from "@trackunit/ui-icons";
3
3
  import { ReactElement, ReactNode } from "react";
4
+ import { CommonProps } from "../../common/CommonProps";
4
5
  import { Refable } from "../../common/Refable";
5
- export interface TabChildProps {
6
+ import type { Styleable } from "../../common/Styleable";
7
+ export interface TabChildProps extends CommonProps {
6
8
  children?: ReactNode;
7
- className?: string;
8
9
  }
9
- export interface TabProps extends TabsTriggerProps, Refable<HTMLButtonElement> {
10
+ export interface TabProps extends TabsTriggerProps, CommonProps, Styleable, Refable<HTMLButtonElement> {
10
11
  /**
11
12
  * Specifies whether the tab should occupy the full width of its container.
12
13
  */
13
14
  isFullWidth?: boolean;
14
- /**
15
- * A data-testid attribute for testing purposes.
16
- */
17
- "data-testid"?: string;
18
15
  /**
19
16
  * The text content of the tab.
20
17
  */
@@ -51,6 +48,10 @@ export interface TabProps extends TabsTriggerProps, Refable<HTMLButtonElement> {
51
48
  * ### When not to use
52
49
  * Do not use Tab outside of a `TabList`/`Tabs` context. For standalone buttons, use `Button`.
53
50
  *
51
+ * ### `data-testid` and `asChild`
52
+ * `data-testid` is applied to the rendered element. When `asChild` is used and the child already
53
+ * defines its own `data-testid`, the child's value wins so consumers can label the actual DOM node.
54
+ *
54
55
  * @example Tab with icon and badge suffix
55
56
  * ```tsx
56
57
  * import { Tabs, TabList, Tab, TabContent, Badge } from "@trackunit/react-components";
@@ -1,8 +1,10 @@
1
1
  import { TabsContentProps } from "@radix-ui/react-tabs";
2
2
  import { IconName } from "@trackunit/ui-icons";
3
3
  import { ReactElement, ReactNode } from "react";
4
+ import { CommonProps } from "../../common/CommonProps";
4
5
  import { Refable } from "../../common/Refable";
5
- export interface TabContentProps extends TabsContentProps, Refable<HTMLDivElement> {
6
+ import type { Styleable } from "../../common/Styleable";
7
+ export interface TabContentProps extends TabsContentProps, CommonProps, Styleable, Refable<HTMLDivElement> {
6
8
  /**
7
9
  * A custom class name to be attached to the content wrapper element.
8
10
  */
@@ -1,20 +1,14 @@
1
1
  import { TabsListProps } from "@radix-ui/react-tabs";
2
2
  import { ReactElement } from "react";
3
+ import { CommonProps } from "../../common/CommonProps";
3
4
  import { Refable } from "../../common/Refable";
5
+ import type { Styleable } from "../../common/Styleable";
4
6
  import { TabContentProps } from "./TabContent";
5
- export interface TabListProps extends TabsListProps, Refable<HTMLDivElement> {
7
+ export interface TabListProps extends TabsListProps, CommonProps, Styleable, Refable<HTMLDivElement> {
6
8
  /**
7
9
  * The child elements to be displayed within the Tabs component.
8
10
  */
9
11
  children: Array<ReactElement<TabContentProps>>;
10
- /**
11
- * A custom class name to be attached to root element
12
- */
13
- className?: string;
14
- /**
15
- * An ID that can be used in tests to locate the component.
16
- */
17
- "data-testid"?: string;
18
12
  /**
19
13
  * If set to true, automatically scrolls the active tab into view when changed.
20
14
  *
@@ -1,7 +1,9 @@
1
1
  import { TabsProps as TabsRootProps } from "@radix-ui/react-tabs";
2
2
  import { ReactElement, ReactNode } from "react";
3
+ import { CommonProps } from "../../common/CommonProps";
3
4
  import { Refable } from "../../common/Refable";
4
- export interface TabsProps extends TabsRootProps, Refable<HTMLDivElement> {
5
+ import type { Styleable } from "../../common/Styleable";
6
+ export interface TabsProps extends TabsRootProps, CommonProps, Styleable, Refable<HTMLDivElement> {
5
7
  /**
6
8
  * If set to true, forces the content of all tabs to render even if not selected.
7
9
  */
@@ -14,14 +16,6 @@ export interface TabsProps extends TabsRootProps, Refable<HTMLDivElement> {
14
16
  * The child elements to be displayed within the Tabs component.
15
17
  */
16
18
  children: ReactNode;
17
- /**
18
- * A custom class name to be attached to root element
19
- */
20
- className?: string;
21
- /**
22
- * An ID that can be used in tests to locate the component.
23
- */
24
- "data-testid"?: string;
25
19
  }
26
20
  /**
27
21
  * Tabs group different but related content, allowing users to navigate views without leaving the page.
@@ -1,11 +1,12 @@
1
1
  import { ActivityColors, CriticalityColors, GeneralColors, IntentColors } from "@trackunit/ui-design-tokens";
2
- import { MouseEventHandler, ReactElement, ReactNode, Ref } from "react";
2
+ import { MouseEventHandler, ReactElement, ReactNode } from "react";
3
3
  import { CommonProps } from "../../common/CommonProps";
4
- import type { Styleable } from "../../common/Styleable";
4
+ import { Refable } from "../../common/Refable";
5
5
  import { Size } from "../../common/Size";
6
+ import type { Styleable } from "../../common/Styleable";
6
7
  export type TagSize = Extract<Size, "small" | "medium">;
7
8
  export type TagColors = IntentColors | GeneralColors | CriticalityColors | ActivityColors;
8
- export interface TagProps extends CommonProps, Styleable {
9
+ export interface TagProps extends CommonProps, Styleable, Refable<HTMLDivElement> {
9
10
  /**
10
11
  * The color of the tag.
11
12
  *
@@ -31,10 +32,6 @@ export interface TagProps extends CommonProps, Styleable {
31
32
  * Is the tag disabled.
32
33
  */
33
34
  disabled?: boolean;
34
- /**
35
- * A ref for the component
36
- */
37
- ref?: Ref<HTMLDivElement>;
38
35
  /**
39
36
  * The icon to display. Only supported for medium size.
40
37
  */
@@ -1,8 +1,9 @@
1
1
  import { ReactElement } from "react";
2
2
  import { CommonProps } from "../../common/CommonProps";
3
+ import { Refable } from "../../common/Refable";
3
4
  import type { Styleable } from "../../common/Styleable";
4
5
  import type { SegmentedValueBarSize, ValueBarSegment } from "./SegmentedValueBarTypes";
5
- export interface SegmentedValueBarProps extends CommonProps, Styleable {
6
+ export interface SegmentedValueBarProps extends CommonProps, Styleable, Refable<HTMLSpanElement> {
6
7
  /**
7
8
  * Array of segments to display. Each segment has a numeric value and a color and optionally a label.
8
9
  * Segments render in the order they are provided; the component does not sort or reorder them.
@@ -50,4 +51,4 @@ export interface SegmentedValueBarProps extends CommonProps, Styleable {
50
51
  * SegmentedValueBar displays multiple colored segments on a bar to visualize values relative to a total.
51
52
  * Supports optional tooltips per segment, showing value and optionally a label.
52
53
  */
53
- export declare const SegmentedValueBar: ({ segments, total, size, showValue, displayValue, unit, valueColor, showTooltip, tooltipUnit, valueWidth, className, "data-testid": dataTestId, style, }: SegmentedValueBarProps) => ReactElement;
54
+ export declare const SegmentedValueBar: ({ segments, total, size, showValue, displayValue, unit, valueColor, showTooltip, tooltipUnit, valueWidth, className, "data-testid": dataTestId, ref, style, }: SegmentedValueBarProps) => ReactElement;
@@ -1,8 +1,9 @@
1
1
  import type { MappedOmit } from "@trackunit/shared-utils";
2
- import { ReactElement, ReactNode, Ref } from "react";
2
+ import { ReactElement, ReactNode } from "react";
3
+ import { Refable } from "../../../common/Refable";
3
4
  import { Size } from "../../../common/Size";
4
5
  import { ButtonCommonProps, ButtonType } from "../shared/ButtonProps";
5
- export interface ButtonProps extends MappedOmit<ButtonCommonProps, "size"> {
6
+ export interface ButtonProps extends MappedOmit<ButtonCommonProps, "size">, Refable<HTMLButtonElement> {
6
7
  /**
7
8
  * Child nodes. Can be used to pass in text to be displayed on the button.
8
9
  */
@@ -35,10 +36,6 @@ export interface ButtonProps extends MappedOmit<ButtonCommonProps, "size"> {
35
36
  * The name of the button
36
37
  */
37
38
  name?: string;
38
- /**
39
- * A ref for the component
40
- */
41
- ref?: Ref<HTMLButtonElement>;
42
39
  /**
43
40
  * The size of the button
44
41
  */
@@ -1,8 +1,9 @@
1
1
  import type { MappedOmit } from "@trackunit/shared-utils";
2
- import { ComponentType, JSX, ReactElement, ReactNode, Ref } from "react";
2
+ import { ComponentType, JSX, ReactElement, ReactNode } from "react";
3
+ import { Refable } from "../../../common/Refable";
3
4
  import { Size } from "../../../common/Size";
4
5
  import { ButtonCommonProps } from "../shared/ButtonProps";
5
- export interface IconButtonProps extends MappedOmit<ButtonCommonProps, "size"> {
6
+ export interface IconButtonProps extends MappedOmit<ButtonCommonProps, "size">, Refable<HTMLButtonElement> {
6
7
  /**
7
8
  * The icon to display.
8
9
  */
@@ -17,10 +18,6 @@ export interface IconButtonProps extends MappedOmit<ButtonCommonProps, "size"> {
17
18
  * The size of the button. Defaults to "medium".
18
19
  */
19
20
  size?: Size;
20
- /**
21
- * A ref for the component
22
- */
23
- ref?: Ref<HTMLButtonElement>;
24
21
  }
25
22
  /**
26
23
  * Buttons are clickable elements that are used to trigger actions. They communicate calls to action to the user and allow users to interact with pages in a variety of ways. The Icon Button is a version of the standard Button component without the text label.
@@ -1,6 +1,8 @@
1
1
  import { MouseEventHandler, ReactElement } from "react";
2
+ import { CommonProps } from "../../../common/CommonProps";
2
3
  import { Refable } from "../../../common/Refable";
3
- interface StarButtonProps extends Refable<HTMLDivElement> {
4
+ import type { Styleable } from "../../../common/Styleable";
5
+ interface StarButtonProps extends CommonProps, Styleable, Refable<HTMLDivElement> {
4
6
  /**
5
7
  * Whether the item is currently starred/favorited. Controls the icon color (primary when starred, neutral when not).
6
8
  */
@@ -33,5 +35,5 @@ interface StarButtonProps extends Refable<HTMLDivElement> {
33
35
  * @param {StarButtonProps} props - The props for the StarButton component
34
36
  * @returns {ReactElement} StarButton component
35
37
  */
36
- export declare const StarButton: ({ starred, onClick, ref }: StarButtonProps) => ReactElement;
38
+ export declare const StarButton: ({ starred, onClick, className, "data-testid": dataTestId, style, ref, }: StarButtonProps) => ReactElement;
37
39
  export {};