@trackunit/react-components 1.22.27 → 1.22.29

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 = {
@@ -1558,15 +1558,16 @@ const Breadcrumb = ({ className, "data-testid": dataTestId, breadcrumbItems, onC
1558
1558
  *
1559
1559
  * @param {BreadcrumbContainerProps} props - The props for the BreadcrumbContainer component
1560
1560
  */
1561
- const BreadcrumbContainer = ({ "data-testid": dataTestId, breadcrumbItems, }) => {
1561
+ const BreadcrumbContainer = ({ className, "data-testid": dataTestId, breadcrumbItems, style, ref, }) => {
1562
1562
  const { isMd: isMediumScreen, isXs: isSmallScreen } = useViewportBreakpoints();
1563
+ const sharedContainerProps = { className, "data-testid": dataTestId, ref, style };
1563
1564
  if (isMediumScreen) {
1564
- return jsxRuntime.jsx(BreadcrumbForMediumScreen, { breadcrumbItems: breadcrumbItems, "data-testid": `mediumScreen-${dataTestId}` });
1565
+ return (jsxRuntime.jsx("div", { ...sharedContainerProps, children: jsxRuntime.jsx(BreadcrumbForMediumScreen, { breadcrumbItems: breadcrumbItems, "data-testid": `mediumScreen-${dataTestId}` }) }));
1565
1566
  }
1566
1567
  if (isSmallScreen) {
1567
- return jsxRuntime.jsx(BreadcrumbForSmallScreen, { breadcrumbItems: breadcrumbItems, "data-testid": `smallScreen-${dataTestId}` });
1568
+ return (jsxRuntime.jsx("div", { ...sharedContainerProps, children: jsxRuntime.jsx(BreadcrumbForSmallScreen, { breadcrumbItems: breadcrumbItems, "data-testid": `smallScreen-${dataTestId}` }) }));
1568
1569
  }
1569
- return jsxRuntime.jsx(BreadcrumbForLargeScreen, { breadcrumbItems: breadcrumbItems, "data-testid": `largeScreen-${dataTestId}` });
1570
+ return (jsxRuntime.jsx("div", { ...sharedContainerProps, children: jsxRuntime.jsx(BreadcrumbForLargeScreen, { breadcrumbItems: breadcrumbItems, "data-testid": `largeScreen-${dataTestId}` }) }));
1570
1571
  };
1571
1572
 
1572
1573
  /**
@@ -1592,8 +1593,8 @@ const BreadcrumbContainer = ({ "data-testid": dataTestId, breadcrumbItems, }) =>
1592
1593
  * @param {StarButtonProps} props - The props for the StarButton component
1593
1594
  * @returns {ReactElement} StarButton component
1594
1595
  */
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" }) }));
1596
+ const StarButton = ({ starred, onClick, className, "data-testid": dataTestId, style, ref, }) => {
1597
+ 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
1598
  };
1598
1599
 
1599
1600
  const cvaCard = cssClassVarianceUtilities.cvaMerge([
@@ -3685,9 +3686,10 @@ function createGrid() {
3685
3686
  * }
3686
3687
  * ```
3687
3688
  */
3688
- function GridAreas({ slots, css, containerProps, validationRef, className, style, children, asChild = false, }) {
3689
+ function GridAreas({ slots, css, containerProps, validationRef, className, "data-testid": dataTestId, ref, style, children, asChild = false, }) {
3689
3690
  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) })] }));
3691
+ const mergedRef = useMergeRefs([validationRef, ref]);
3692
+ 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
3693
  }
3692
3694
 
3693
3695
  /**
@@ -5666,15 +5668,16 @@ const ListLoadingIndicator = ({ type, hasThumbnail, thumbnailShape, hasDescripti
5666
5668
  * );
5667
5669
  * ```
5668
5670
  */
5669
- const List = ({ children, className, "data-testid": dataTestId, style,
5671
+ const List = ({ children, className, "data-testid": dataTestId, style, ref,
5670
5672
  // UseListResult properties
5671
5673
  containerRef, listRef, rows, getListItemProps, header, loadingIndicator, shouldShowLoaderAtIndex, count, isScrolling, separator, topSeparatorOnScroll, isAtTop, contentFillsContainer,
5672
5674
  // Unused but part of UseListResult interface (can be used from parent)
5673
5675
  scrollOffset: _scrollOffset, getTotalSize: _getTotalSize, getVirtualItems: _getVirtualItems, scrollToOffset: _scrollToOffset, scrollToIndex: _scrollToIndex, measure: _measure, }) => {
5676
+ const mergedContainerRef = useMergeRefs([containerRef, ref]);
5674
5677
  return (jsxRuntime.jsx("div", { className: cvaListContainer({
5675
5678
  withTopSeparator: topSeparatorOnScroll && !isAtTop,
5676
5679
  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 => {
5680
+ }), "data-is-scrolling": isScrolling, "data-testid": dataTestId, ref: mergedContainerRef, style: style, children: jsxRuntime.jsx("ul", { className: cvaList(), ref: listRef, children: rows.map(row => {
5678
5681
  // Generate list item props with clean separator styling
5679
5682
  const listItemProps = getListItemProps(row, {
5680
5683
  className: cvaListItem$1({
@@ -6216,8 +6219,8 @@ const cvaMenuListItem = cssClassVarianceUtilities.cvaMerge("max-w-full");
6216
6219
  * ```
6217
6220
  * @returns {ReactElement} MenuDivider component
6218
6221
  */
6219
- const MenuDivider = ({ style }) => {
6220
- return jsxRuntime.jsx("div", { className: cvaMenuListDivider(), "data-testid": "menu-divider", style: style });
6222
+ const MenuDivider = ({ className, style, "data-testid": dataTestId, ref }) => {
6223
+ return (jsxRuntime.jsx("div", { className: cvaMenuListDivider({ className }), "data-testid": dataTestId ?? "menu-divider", ref: ref, style: style }));
6221
6224
  };
6222
6225
 
6223
6226
  /**
@@ -6890,9 +6893,13 @@ function ActionRenderer({ action, isMenuItem = false, externalOnClick }) {
6890
6893
  * @param {Array<PageHeaderSecondaryActionType>} props.actions - The secondary actions to render
6891
6894
  * @param {boolean} [props.hasPrimaryAction] - Whether there is a primary action present
6892
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
6893
6900
  * @returns {ReactElement | null} PageHeaderSecondaryActions component
6894
6901
  */
6895
- const PageHeaderSecondaryActions = ({ actions, hasPrimaryAction = false, groupActions = false, }) => {
6902
+ const PageHeaderSecondaryActions = ({ actions, hasPrimaryAction = false, groupActions = false, className, "data-testid": dataTestId, style, ref, }) => {
6896
6903
  const enabledActions = react.useMemo(() => actions.filter(action => action.hidden === false || action.hidden === undefined), [actions]);
6897
6904
  // If there are no enabled actions, don't render anything
6898
6905
  if (enabledActions.length === 0) {
@@ -6909,10 +6916,10 @@ const PageHeaderSecondaryActions = ({ actions, hasPrimaryAction = false, groupAc
6909
6916
  return [danger, [...others, action]];
6910
6917
  }
6911
6918
  }, [[], []]);
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}`)))] })) }));
6919
+ 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
6920
  }
6914
6921
  // Otherwise, render them inline as buttons
6915
- return (jsxRuntime.jsx("div", { className: "flex flex-row items-center gap-2", children: enabledActions
6922
+ 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
6923
  .toSorted((a, b) => {
6917
6924
  if (a.variant === "secondary" && b.variant === "secondary-danger") {
6918
6925
  return 1;
@@ -9900,6 +9907,10 @@ const cvaTab = cssClassVarianceUtilities.cvaMerge([
9900
9907
  * ### When not to use
9901
9908
  * Do not use Tab outside of a `TabList`/`Tabs` context. For standalone buttons, use `Button`.
9902
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
+ *
9903
9914
  * @example Tab with icon and badge suffix
9904
9915
  * ```tsx
9905
9916
  * import { Tabs, TabList, Tab, TabContent, Badge } from "@trackunit/react-components";
@@ -9922,14 +9933,15 @@ const cvaTab = cssClassVarianceUtilities.cvaMerge([
9922
9933
  */
9923
9934
  const Tab = ({ value, isFullWidth = false, iconName = undefined, "data-testid": dataTestId, className, children, suffix, asChild = false, appendTabStylesToChildIfAsChild = true, ref, ...rest }) => {
9924
9935
  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 = {
9936
+ const sharedProps = {
9926
9937
  className: appendTabStylesToChildIfAsChild ? cvaTab({ className, isFullWidth }) : className,
9927
9938
  ...rest,
9928
9939
  };
9929
9940
  return (jsxRuntime.jsx(reactTabs.Trigger, { asChild: true, ref: ref, value: value, children: asChild && typeof children !== "string" ? (react.cloneElement(children, {
9930
- ...commonProps,
9941
+ ...sharedProps,
9942
+ "data-testid": children.props["data-testid"] ?? dataTestId,
9931
9943
  children: renderContent(),
9932
- })) : (jsxRuntime.jsx("button", { ...commonProps, "data-testid": dataTestId, children: renderContent() })) }));
9944
+ })) : (jsxRuntime.jsx("button", { ...sharedProps, "data-testid": dataTestId, children: renderContent() })) }));
9933
9945
  };
9934
9946
 
9935
9947
  /**
@@ -10373,13 +10385,13 @@ const getValueTextVariant = (size, sum, segments, total) => {
10373
10385
  * SegmentedValueBar displays multiple colored segments on a bar to visualize values relative to a total.
10374
10386
  * Supports optional tooltips per segment, showing value and optionally a label.
10375
10387
  */
10376
- 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, }) => {
10377
10389
  const computedSegments = computeSegments(segments, total);
10378
10390
  const sum = total > 0 ? computeSum(segments) : 0;
10379
10391
  const valueText = formatValue(displayValue ?? sum, unit);
10380
10392
  const canShowValue = showValue && size !== "extraSmall";
10381
10393
  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) => {
10394
+ 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
10395
  const tooltipLabel = segment.label
10384
10396
  ? `${segment.label}: ${formatValue(segment.value, tooltipUnit ?? unit)}`
10385
10397
  : formatValue(segment.value, tooltipUnit ?? unit);
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 = {
@@ -1556,15 +1556,16 @@ const Breadcrumb = ({ className, "data-testid": dataTestId, breadcrumbItems, onC
1556
1556
  *
1557
1557
  * @param {BreadcrumbContainerProps} props - The props for the BreadcrumbContainer component
1558
1558
  */
1559
- const BreadcrumbContainer = ({ "data-testid": dataTestId, breadcrumbItems, }) => {
1559
+ const BreadcrumbContainer = ({ className, "data-testid": dataTestId, breadcrumbItems, style, ref, }) => {
1560
1560
  const { isMd: isMediumScreen, isXs: isSmallScreen } = useViewportBreakpoints();
1561
+ const sharedContainerProps = { className, "data-testid": dataTestId, ref, style };
1561
1562
  if (isMediumScreen) {
1562
- return jsx(BreadcrumbForMediumScreen, { breadcrumbItems: breadcrumbItems, "data-testid": `mediumScreen-${dataTestId}` });
1563
+ return (jsx("div", { ...sharedContainerProps, children: jsx(BreadcrumbForMediumScreen, { breadcrumbItems: breadcrumbItems, "data-testid": `mediumScreen-${dataTestId}` }) }));
1563
1564
  }
1564
1565
  if (isSmallScreen) {
1565
- return jsx(BreadcrumbForSmallScreen, { breadcrumbItems: breadcrumbItems, "data-testid": `smallScreen-${dataTestId}` });
1566
+ return (jsx("div", { ...sharedContainerProps, children: jsx(BreadcrumbForSmallScreen, { breadcrumbItems: breadcrumbItems, "data-testid": `smallScreen-${dataTestId}` }) }));
1566
1567
  }
1567
- return jsx(BreadcrumbForLargeScreen, { breadcrumbItems: breadcrumbItems, "data-testid": `largeScreen-${dataTestId}` });
1568
+ return (jsx("div", { ...sharedContainerProps, children: jsx(BreadcrumbForLargeScreen, { breadcrumbItems: breadcrumbItems, "data-testid": `largeScreen-${dataTestId}` }) }));
1568
1569
  };
1569
1570
 
1570
1571
  /**
@@ -1590,8 +1591,8 @@ const BreadcrumbContainer = ({ "data-testid": dataTestId, breadcrumbItems, }) =>
1590
1591
  * @param {StarButtonProps} props - The props for the StarButton component
1591
1592
  * @returns {ReactElement} StarButton component
1592
1593
  */
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" }) }));
1594
+ const StarButton = ({ starred, onClick, className, "data-testid": dataTestId, style, ref, }) => {
1595
+ 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
1596
  };
1596
1597
 
1597
1598
  const cvaCard = cvaMerge([
@@ -3683,9 +3684,10 @@ function createGrid() {
3683
3684
  * }
3684
3685
  * ```
3685
3686
  */
3686
- function GridAreas({ slots, css, containerProps, validationRef, className, style, children, asChild = false, }) {
3687
+ function GridAreas({ slots, css, containerProps, validationRef, className, "data-testid": dataTestId, ref, style, children, asChild = false, }) {
3687
3688
  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) })] }));
3689
+ const mergedRef = useMergeRefs([validationRef, ref]);
3690
+ 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
3691
  }
3690
3692
 
3691
3693
  /**
@@ -5664,15 +5666,16 @@ const ListLoadingIndicator = ({ type, hasThumbnail, thumbnailShape, hasDescripti
5664
5666
  * );
5665
5667
  * ```
5666
5668
  */
5667
- const List = ({ children, className, "data-testid": dataTestId, style,
5669
+ const List = ({ children, className, "data-testid": dataTestId, style, ref,
5668
5670
  // UseListResult properties
5669
5671
  containerRef, listRef, rows, getListItemProps, header, loadingIndicator, shouldShowLoaderAtIndex, count, isScrolling, separator, topSeparatorOnScroll, isAtTop, contentFillsContainer,
5670
5672
  // Unused but part of UseListResult interface (can be used from parent)
5671
5673
  scrollOffset: _scrollOffset, getTotalSize: _getTotalSize, getVirtualItems: _getVirtualItems, scrollToOffset: _scrollToOffset, scrollToIndex: _scrollToIndex, measure: _measure, }) => {
5674
+ const mergedContainerRef = useMergeRefs([containerRef, ref]);
5672
5675
  return (jsx("div", { className: cvaListContainer({
5673
5676
  withTopSeparator: topSeparatorOnScroll && !isAtTop,
5674
5677
  className,
5675
- }), "data-is-scrolling": isScrolling, "data-testid": dataTestId, ref: containerRef, style: style, children: jsx("ul", { className: cvaList(), ref: listRef, children: rows.map(row => {
5678
+ }), "data-is-scrolling": isScrolling, "data-testid": dataTestId, ref: mergedContainerRef, style: style, children: jsx("ul", { className: cvaList(), ref: listRef, children: rows.map(row => {
5676
5679
  // Generate list item props with clean separator styling
5677
5680
  const listItemProps = getListItemProps(row, {
5678
5681
  className: cvaListItem$1({
@@ -6214,8 +6217,8 @@ const cvaMenuListItem = cvaMerge("max-w-full");
6214
6217
  * ```
6215
6218
  * @returns {ReactElement} MenuDivider component
6216
6219
  */
6217
- const MenuDivider = ({ style }) => {
6218
- return jsx("div", { className: cvaMenuListDivider(), "data-testid": "menu-divider", style: style });
6220
+ const MenuDivider = ({ className, style, "data-testid": dataTestId, ref }) => {
6221
+ return (jsx("div", { className: cvaMenuListDivider({ className }), "data-testid": dataTestId ?? "menu-divider", ref: ref, style: style }));
6219
6222
  };
6220
6223
 
6221
6224
  /**
@@ -6888,9 +6891,13 @@ function ActionRenderer({ action, isMenuItem = false, externalOnClick }) {
6888
6891
  * @param {Array<PageHeaderSecondaryActionType>} props.actions - The secondary actions to render
6889
6892
  * @param {boolean} [props.hasPrimaryAction] - Whether there is a primary action present
6890
6893
  * @param {boolean} [props.groupActions] - Whether to group actions in a More Menu regardless of action count
6894
+ * @param {string} [props.className] - A custom class name for the action container
6895
+ * @param {string} [props."data-testid"] - An ID used to locate the container in tests
6896
+ * @param {object} [props.style] - Inline styles for the action container
6897
+ * @param {unknown} [props.ref] - Ref for the action container element
6891
6898
  * @returns {ReactElement | null} PageHeaderSecondaryActions component
6892
6899
  */
6893
- const PageHeaderSecondaryActions = ({ actions, hasPrimaryAction = false, groupActions = false, }) => {
6900
+ const PageHeaderSecondaryActions = ({ actions, hasPrimaryAction = false, groupActions = false, className, "data-testid": dataTestId, style, ref, }) => {
6894
6901
  const enabledActions = useMemo(() => actions.filter(action => action.hidden === false || action.hidden === undefined), [actions]);
6895
6902
  // If there are no enabled actions, don't render anything
6896
6903
  if (enabledActions.length === 0) {
@@ -6907,10 +6914,10 @@ const PageHeaderSecondaryActions = ({ actions, hasPrimaryAction = false, groupAc
6907
6914
  return [danger, [...others, action]];
6908
6915
  }
6909
6916
  }, [[], []]);
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}`)))] })) }));
6917
+ 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
6918
  }
6912
6919
  // Otherwise, render them inline as buttons
6913
- return (jsx("div", { className: "flex flex-row items-center gap-2", children: enabledActions
6920
+ return (jsx("div", { className: twMerge("flex flex-row items-center gap-2", className), "data-testid": dataTestId, ref: ref, style: style, children: enabledActions
6914
6921
  .toSorted((a, b) => {
6915
6922
  if (a.variant === "secondary" && b.variant === "secondary-danger") {
6916
6923
  return 1;
@@ -9898,6 +9905,10 @@ const cvaTab = cvaMerge([
9898
9905
  * ### When not to use
9899
9906
  * Do not use Tab outside of a `TabList`/`Tabs` context. For standalone buttons, use `Button`.
9900
9907
  *
9908
+ * ### `data-testid` and `asChild`
9909
+ * `data-testid` is applied to the rendered element. When `asChild` is used and the child already
9910
+ * defines its own `data-testid`, the child's value wins so consumers can label the actual DOM node.
9911
+ *
9901
9912
  * @example Tab with icon and badge suffix
9902
9913
  * ```tsx
9903
9914
  * import { Tabs, TabList, Tab, TabContent, Badge } from "@trackunit/react-components";
@@ -9920,14 +9931,15 @@ const cvaTab = cvaMerge([
9920
9931
  */
9921
9932
  const Tab = ({ value, isFullWidth = false, iconName = undefined, "data-testid": dataTestId, className, children, suffix, asChild = false, appendTabStylesToChildIfAsChild = true, ref, ...rest }) => {
9922
9933
  const renderContent = () => (jsxs(Fragment, { children: [iconName !== undefined ? jsx(Icon, { name: iconName, size: "small" }) : null, isValidElement(children) ? children.props.children : children, suffix] }));
9923
- const commonProps = {
9934
+ const sharedProps = {
9924
9935
  className: appendTabStylesToChildIfAsChild ? cvaTab({ className, isFullWidth }) : className,
9925
9936
  ...rest,
9926
9937
  };
9927
9938
  return (jsx(Trigger, { asChild: true, ref: ref, value: value, children: asChild && typeof children !== "string" ? (cloneElement(children, {
9928
- ...commonProps,
9939
+ ...sharedProps,
9940
+ "data-testid": children.props["data-testid"] ?? dataTestId,
9929
9941
  children: renderContent(),
9930
- })) : (jsx("button", { ...commonProps, "data-testid": dataTestId, children: renderContent() })) }));
9942
+ })) : (jsx("button", { ...sharedProps, "data-testid": dataTestId, children: renderContent() })) }));
9931
9943
  };
9932
9944
 
9933
9945
  /**
@@ -10371,13 +10383,13 @@ const getValueTextVariant = (size, sum, segments, total) => {
10371
10383
  * SegmentedValueBar displays multiple colored segments on a bar to visualize values relative to a total.
10372
10384
  * Supports optional tooltips per segment, showing value and optionally a label.
10373
10385
  */
10374
- const SegmentedValueBar = ({ segments, total, size = "small", showValue = false, displayValue, unit, valueColor, showTooltip = false, tooltipUnit, valueWidth, className, "data-testid": dataTestId, style, }) => {
10386
+ const SegmentedValueBar = ({ segments, total, size = "small", showValue = false, displayValue, unit, valueColor, showTooltip = false, tooltipUnit, valueWidth, className, "data-testid": dataTestId, ref, style, }) => {
10375
10387
  const computedSegments = computeSegments(segments, total);
10376
10388
  const sum = total > 0 ? computeSum(segments) : 0;
10377
10389
  const valueText = formatValue(displayValue ?? sum, unit);
10378
10390
  const canShowValue = showValue && size !== "extraSmall";
10379
10391
  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) => {
10392
+ 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
10393
  const tooltipLabel = segment.label
10382
10394
  ? `${segment.label}: ${formatValue(segment.value, tooltipUnit ?? unit)}`
10383
10395
  : formatValue(segment.value, tooltipUnit ?? unit);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trackunit/react-components",
3
- "version": "1.22.27",
3
+ "version": "1.22.29",
4
4
  "repository": "https://github.com/Trackunit/manager",
5
5
  "license": "SEE LICENSE IN LICENSE.txt",
6
6
  "engines": {
@@ -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 {};
@@ -42,4 +42,4 @@ export declare const Breadcrumb: ({ className, "data-testid": dataTestId, breadc
42
42
  *
43
43
  * @param {BreadcrumbContainerProps} props - The props for the BreadcrumbContainer component
44
44
  */
45
- export declare const BreadcrumbContainer: ({ "data-testid": dataTestId, breadcrumbItems, }: BreadcrumbContainerProps) => ReactElement;
45
+ 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;
@@ -1,11 +1,14 @@
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 = {
11
+ type ActionRendererProps = CommonProps & Styleable & Refable & {
9
12
  action: PageHeaderSecondaryActionType;
10
13
  /**
11
14
  * Indicates if we should render a MenuItem or Button.
@@ -32,7 +35,11 @@ export declare function ActionRenderer({ action, isMenuItem, externalOnClick }:
32
35
  * @param {Array<PageHeaderSecondaryActionType>} props.actions - The secondary actions to render
33
36
  * @param {boolean} [props.hasPrimaryAction] - Whether there is a primary action present
34
37
  * @param {boolean} [props.groupActions] - Whether to group actions in a More Menu regardless of action count
38
+ * @param {string} [props.className] - A custom class name for the action container
39
+ * @param {string} [props."data-testid"] - An ID used to locate the container in tests
40
+ * @param {object} [props.style] - Inline styles for the action container
41
+ * @param {unknown} [props.ref] - Ref for the action container element
35
42
  * @returns {ReactElement | null} PageHeaderSecondaryActions component
36
43
  */
37
- export declare const PageHeaderSecondaryActions: ({ actions, hasPrimaryAction, groupActions, }: PageHeaderSecondaryActionsProps) => ReactElement | null;
44
+ export declare const PageHeaderSecondaryActions: ({ actions, hasPrimaryAction, groupActions, className, "data-testid": dataTestId, style, ref, }: PageHeaderSecondaryActionsProps) => ReactElement | null;
38
45
  export {};
@@ -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 {};