@trackunit/react-components 1.17.20 → 1.17.22

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
@@ -7004,6 +7004,69 @@ const ToggleButton = ({ title, size, children, "data-testid": dataTestId, classN
7004
7004
  return (jsxRuntime.jsx("button", { className: tailwindMerge.twMerge("flex items-center justify-center gap-1 self-stretch", paddingClasses, className), "data-testid": dataTestId, title: isIconOnly ? title : undefined, type: "button", ...rest, children: isIconOnly ? (icon) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [iconPrefix, children] })) }));
7005
7005
  };
7006
7006
 
7007
+ const cvaSegmentedValueBar = cssClassVarianceUtilities.cvaMerge(["w-full", "overflow-hidden", "rounded", "bg-neutral-100", "flex"], {
7008
+ variants: {
7009
+ size: {
7010
+ extraSmall: "h-1",
7011
+ small: "h-3",
7012
+ large: "h-9",
7013
+ },
7014
+ },
7015
+ defaultVariants: {
7016
+ size: "small",
7017
+ },
7018
+ });
7019
+
7020
+ /** Minimum fraction of the total a segment must occupy for value text to be overlaid inside it. */
7021
+ const MIN_RATIO_FOR_OVERLAY_ALIGNMENT = 0.2;
7022
+ const filterPositive = (segments) => segments.filter(segment => segment.value > 0);
7023
+ /** Formats a numeric value to one decimal place with an optional unit suffix. */
7024
+ const formatValue = (value, unit) => `${Number(value.toFixed(1))}${unit ?? ""}`;
7025
+ /** Returns the sum of all positive segment values. */
7026
+ const computeSum = (segments) => filterPositive(segments).reduce((acc, segment) => acc + segment.value, 0);
7027
+ /** Computes percentage widths for each positive segment, scaling proportionally when the sum exceeds total. */
7028
+ const computeSegments = (segments, total) => {
7029
+ if (total <= 0)
7030
+ return [];
7031
+ const positiveSegments = filterPositive(segments);
7032
+ const sum = computeSum(segments);
7033
+ const scale = sum > total ? total / sum : 1;
7034
+ return positiveSegments.map(segment => ({
7035
+ width: (segment.value / total) * scale * 100,
7036
+ value: segment.value,
7037
+ color: segment.color,
7038
+ label: segment.label,
7039
+ }));
7040
+ };
7041
+ /** Determines the text variant size for value overlay placement on the bar. */
7042
+ const getValueTextVariant = (size, sum, segments, total) => {
7043
+ if (size !== "large" || sum <= 0)
7044
+ return "small";
7045
+ const firstPositiveSegment = segments.find(segment => segment.value > 0);
7046
+ const isFirstSegmentWideEnoughForOverlay = firstPositiveSegment !== undefined &&
7047
+ total > 0 &&
7048
+ firstPositiveSegment.value / total >= MIN_RATIO_FOR_OVERLAY_ALIGNMENT;
7049
+ return isFirstSegmentWideEnoughForOverlay ? "large" : "small";
7050
+ };
7051
+
7052
+ /**
7053
+ * SegmentedValueBar displays multiple colored segments on a bar to visualize values relative to a total.
7054
+ * Supports optional tooltips per segment, showing value and optionally a label.
7055
+ */
7056
+ const SegmentedValueBar = ({ segments, total, size = "small", showValue = false, unit, valueColor, showTooltip = false, className, "data-testid": dataTestId, }) => {
7057
+ const computedSegments = computeSegments(segments, total);
7058
+ const sum = total > 0 ? computeSum(segments) : 0;
7059
+ const valueText = formatValue(sum, unit);
7060
+ const canShowValue = showValue && size !== "extraSmall";
7061
+ const valueTextClassName = cvaValueBarText({ size: getValueTextVariant(size, sum, segments, total) });
7062
+ return (jsxRuntime.jsxs("span", { className: "relative flex items-center gap-2", "data-testid": dataTestId, children: [jsxRuntime.jsx("div", { "aria-label": valueText, className: cvaSegmentedValueBar({ className, size }), "data-testid": dataTestId ? `${dataTestId}-track` : undefined, children: computedSegments.map((segment, index) => {
7063
+ const tooltipLabel = segment.label
7064
+ ? `${segment.label}: ${formatValue(segment.value, unit)}`
7065
+ : formatValue(segment.value, unit);
7066
+ return showTooltip ? (jsxRuntime.jsx(Tooltip, { label: tooltipLabel, placement: "top", children: jsxRuntime.jsx("div", { "data-testid": dataTestId ? `${dataTestId}-segment-${index}` : undefined, style: { backgroundColor: segment.color, width: `${segment.width}%`, height: "100%" } }) }, index)) : (jsxRuntime.jsx("div", { "data-testid": dataTestId ? `${dataTestId}-segment-${index}` : undefined, style: { backgroundColor: segment.color, width: `${segment.width}%`, height: "100%" } }, index));
7067
+ }) }), canShowValue ? (jsxRuntime.jsx("span", { className: valueTextClassName, "data-testid": dataTestId ? `${dataTestId}-value` : undefined, children: jsxRuntime.jsx("span", { style: valueColor ? { color: valueColor } : undefined, children: valueText }) })) : null] }));
7068
+ };
7069
+
7007
7070
  /**
7008
7071
  * Base64URL encode bytes to a URL-safe string
7009
7072
  */
@@ -8434,6 +8497,7 @@ exports.PreferenceCardSkeleton = PreferenceCardSkeleton;
8434
8497
  exports.Prompt = Prompt;
8435
8498
  exports.ROLE_CARD = ROLE_CARD;
8436
8499
  exports.SectionHeader = SectionHeader;
8500
+ exports.SegmentedValueBar = SegmentedValueBar;
8437
8501
  exports.Sidebar = Sidebar;
8438
8502
  exports.SkeletonBlock = SkeletonBlock;
8439
8503
  exports.SkeletonLabel = SkeletonLabel;
package/index.esm.js CHANGED
@@ -7002,6 +7002,69 @@ const ToggleButton = ({ title, size, children, "data-testid": dataTestId, classN
7002
7002
  return (jsx("button", { className: twMerge("flex items-center justify-center gap-1 self-stretch", paddingClasses, className), "data-testid": dataTestId, title: isIconOnly ? title : undefined, type: "button", ...rest, children: isIconOnly ? (icon) : (jsxs(Fragment$1, { children: [iconPrefix, children] })) }));
7003
7003
  };
7004
7004
 
7005
+ const cvaSegmentedValueBar = cvaMerge(["w-full", "overflow-hidden", "rounded", "bg-neutral-100", "flex"], {
7006
+ variants: {
7007
+ size: {
7008
+ extraSmall: "h-1",
7009
+ small: "h-3",
7010
+ large: "h-9",
7011
+ },
7012
+ },
7013
+ defaultVariants: {
7014
+ size: "small",
7015
+ },
7016
+ });
7017
+
7018
+ /** Minimum fraction of the total a segment must occupy for value text to be overlaid inside it. */
7019
+ const MIN_RATIO_FOR_OVERLAY_ALIGNMENT = 0.2;
7020
+ const filterPositive = (segments) => segments.filter(segment => segment.value > 0);
7021
+ /** Formats a numeric value to one decimal place with an optional unit suffix. */
7022
+ const formatValue = (value, unit) => `${Number(value.toFixed(1))}${unit ?? ""}`;
7023
+ /** Returns the sum of all positive segment values. */
7024
+ const computeSum = (segments) => filterPositive(segments).reduce((acc, segment) => acc + segment.value, 0);
7025
+ /** Computes percentage widths for each positive segment, scaling proportionally when the sum exceeds total. */
7026
+ const computeSegments = (segments, total) => {
7027
+ if (total <= 0)
7028
+ return [];
7029
+ const positiveSegments = filterPositive(segments);
7030
+ const sum = computeSum(segments);
7031
+ const scale = sum > total ? total / sum : 1;
7032
+ return positiveSegments.map(segment => ({
7033
+ width: (segment.value / total) * scale * 100,
7034
+ value: segment.value,
7035
+ color: segment.color,
7036
+ label: segment.label,
7037
+ }));
7038
+ };
7039
+ /** Determines the text variant size for value overlay placement on the bar. */
7040
+ const getValueTextVariant = (size, sum, segments, total) => {
7041
+ if (size !== "large" || sum <= 0)
7042
+ return "small";
7043
+ const firstPositiveSegment = segments.find(segment => segment.value > 0);
7044
+ const isFirstSegmentWideEnoughForOverlay = firstPositiveSegment !== undefined &&
7045
+ total > 0 &&
7046
+ firstPositiveSegment.value / total >= MIN_RATIO_FOR_OVERLAY_ALIGNMENT;
7047
+ return isFirstSegmentWideEnoughForOverlay ? "large" : "small";
7048
+ };
7049
+
7050
+ /**
7051
+ * SegmentedValueBar displays multiple colored segments on a bar to visualize values relative to a total.
7052
+ * Supports optional tooltips per segment, showing value and optionally a label.
7053
+ */
7054
+ const SegmentedValueBar = ({ segments, total, size = "small", showValue = false, unit, valueColor, showTooltip = false, className, "data-testid": dataTestId, }) => {
7055
+ const computedSegments = computeSegments(segments, total);
7056
+ const sum = total > 0 ? computeSum(segments) : 0;
7057
+ const valueText = formatValue(sum, unit);
7058
+ const canShowValue = showValue && size !== "extraSmall";
7059
+ const valueTextClassName = cvaValueBarText({ size: getValueTextVariant(size, sum, segments, total) });
7060
+ return (jsxs("span", { className: "relative flex items-center gap-2", "data-testid": dataTestId, children: [jsx("div", { "aria-label": valueText, className: cvaSegmentedValueBar({ className, size }), "data-testid": dataTestId ? `${dataTestId}-track` : undefined, children: computedSegments.map((segment, index) => {
7061
+ const tooltipLabel = segment.label
7062
+ ? `${segment.label}: ${formatValue(segment.value, unit)}`
7063
+ : formatValue(segment.value, unit);
7064
+ return showTooltip ? (jsx(Tooltip, { label: tooltipLabel, placement: "top", children: jsx("div", { "data-testid": dataTestId ? `${dataTestId}-segment-${index}` : undefined, style: { backgroundColor: segment.color, width: `${segment.width}%`, height: "100%" } }) }, index)) : (jsx("div", { "data-testid": dataTestId ? `${dataTestId}-segment-${index}` : undefined, style: { backgroundColor: segment.color, width: `${segment.width}%`, height: "100%" } }, index));
7065
+ }) }), canShowValue ? (jsx("span", { className: valueTextClassName, "data-testid": dataTestId ? `${dataTestId}-value` : undefined, children: jsx("span", { style: valueColor ? { color: valueColor } : undefined, children: valueText }) })) : null] }));
7066
+ };
7067
+
7005
7068
  /**
7006
7069
  * Base64URL encode bytes to a URL-safe string
7007
7070
  */
@@ -8377,4 +8440,4 @@ const useWindowActivity = ({ onFocus, onBlur, skip = false } = { onBlur: undefin
8377
8440
  return useMemo(() => ({ focused }), [focused]);
8378
8441
  };
8379
8442
 
8380
- 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, MenuDivider, MenuItem, MenuList, MoreMenu, Notice, PackageNameStoryComponent, Page, PageContent, PageHeader, PageHeaderKpiMetrics, PageHeaderSecondaryActions, PageHeaderTitle, Pagination, Polygon, Popover, PopoverContent, PopoverTitle, PopoverTrigger, Portal, PreferenceCard, PreferenceCardSkeleton, Prompt, ROLE_CARD, SectionHeader, 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, useClickOutside, useContainerBreakpoints, useContinuousTimeout, useCopyToClipboard, useCustomEncoding, useDebounce, useDevicePixelRatio, useElevatedReducer, useElevatedState, useGridAreas, useHover, useInfiniteScroll, useIsFirstRender, useIsFullscreen, useIsTextTruncated, useKeyboardShortcut, useList, useListItemHeight, useLocalStorage, useLocalStorageReducer, useMeasure, useMergeRefs, useModifierKey, useOverflowItems, usePopoverContext, usePrevious, usePrompt, useRandomCSSLengths, useRelayPagination, useResize, useScrollBlock, useScrollDetection, useSelfUpdatingRef, useTextSearch, useTimeout, useViewportBreakpoints, useWatch, useWindowActivity };
8443
+ 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, MenuDivider, MenuItem, MenuList, MoreMenu, Notice, PackageNameStoryComponent, Page, PageContent, PageHeader, PageHeaderKpiMetrics, PageHeaderSecondaryActions, PageHeaderTitle, Pagination, Polygon, Popover, PopoverContent, PopoverTitle, PopoverTrigger, Portal, PreferenceCard, PreferenceCardSkeleton, Prompt, ROLE_CARD, SectionHeader, SegmentedValueBar, 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, useClickOutside, useContainerBreakpoints, useContinuousTimeout, useCopyToClipboard, useCustomEncoding, useDebounce, useDevicePixelRatio, useElevatedReducer, useElevatedState, useGridAreas, useHover, useInfiniteScroll, useIsFirstRender, useIsFullscreen, useIsTextTruncated, useKeyboardShortcut, useList, useListItemHeight, useLocalStorage, useLocalStorageReducer, useMeasure, useMergeRefs, useModifierKey, useOverflowItems, usePopoverContext, usePrevious, usePrompt, useRandomCSSLengths, useRelayPagination, useResize, useScrollBlock, useScrollDetection, useSelfUpdatingRef, useTextSearch, useTimeout, useViewportBreakpoints, useWatch, useWindowActivity };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trackunit/react-components",
3
- "version": "1.17.20",
3
+ "version": "1.17.22",
4
4
  "repository": "https://github.com/Trackunit/manager",
5
5
  "license": "SEE LICENSE IN LICENSE.txt",
6
6
  "engines": {
@@ -0,0 +1,40 @@
1
+ import { ReactElement } from "react";
2
+ import { CommonProps } from "../../common/CommonProps";
3
+ import type { SegmentedValueBarSize, ValueBarSegment } from "./SegmentedValueBarTypes";
4
+ export interface SegmentedValueBarProps extends CommonProps {
5
+ /**
6
+ * Array of segments to display. Each segment has a numeric value and a color and optionally a label.
7
+ * Segments render in the order they are provided; the component does not sort or reorder them.
8
+ */
9
+ readonly segments: Array<ValueBarSegment>;
10
+ /**
11
+ * The total value that segments are measured against.
12
+ */
13
+ readonly total: number;
14
+ /**
15
+ * Size of the bar. Determines height and whether value text can be displayed.
16
+ */
17
+ readonly size?: SegmentedValueBarSize;
18
+ /**
19
+ * Show the sum of segment values with unit (if provided) on the bar in large version or after the bar in the small version.
20
+ * Ignored when size is "extraSmall".
21
+ */
22
+ readonly showValue?: boolean;
23
+ /**
24
+ * Unit appended to the displayed value, e.g. "%", "h", "°C".
25
+ */
26
+ readonly unit?: string;
27
+ /**
28
+ * Custom color for the displayed value text.
29
+ */
30
+ readonly valueColor?: string;
31
+ /**
32
+ * Show a tooltip on hover with the individual segment value (and unit if provided). If a segment has a label, it is shown alongside the value.
33
+ */
34
+ readonly showTooltip?: boolean;
35
+ }
36
+ /**
37
+ * SegmentedValueBar displays multiple colored segments on a bar to visualize values relative to a total.
38
+ * Supports optional tooltips per segment, showing value and optionally a label.
39
+ */
40
+ export declare const SegmentedValueBar: ({ segments, total, size, showValue, unit, valueColor, showTooltip, className, "data-testid": dataTestId, }: SegmentedValueBarProps) => ReactElement;
@@ -0,0 +1,3 @@
1
+ export declare const cvaSegmentedValueBar: (props?: ({
2
+ size?: "extraSmall" | "small" | "large" | null | undefined;
3
+ } & import("class-variance-authority/dist/types").ClassProp) | undefined) => string;
@@ -0,0 +1,16 @@
1
+ import type { SegmentedValueBarSize, ValueBarSegment } from "./SegmentedValueBarTypes";
2
+ type ComputedSegment = {
3
+ readonly width: number;
4
+ readonly value: number;
5
+ readonly color: string;
6
+ readonly label?: string;
7
+ };
8
+ /** Formats a numeric value to one decimal place with an optional unit suffix. */
9
+ export declare const formatValue: (value: number, unit?: string) => string;
10
+ /** Returns the sum of all positive segment values. */
11
+ export declare const computeSum: (segments: Array<ValueBarSegment>) => number;
12
+ /** Computes percentage widths for each positive segment, scaling proportionally when the sum exceeds total. */
13
+ export declare const computeSegments: (segments: Array<ValueBarSegment>, total: number) => Array<ComputedSegment>;
14
+ /** Determines the text variant size for value overlay placement on the bar. */
15
+ export declare const getValueTextVariant: (size: SegmentedValueBarSize, sum: number, segments: Array<ValueBarSegment>, total: number) => "small" | "large";
16
+ export {};
@@ -0,0 +1,6 @@
1
+ export type ValueBarSegment = {
2
+ readonly value: number;
3
+ readonly color: string;
4
+ readonly label?: string;
5
+ };
6
+ export type SegmentedValueBarSize = "small" | "large" | "extraSmall";
package/src/index.d.ts CHANGED
@@ -97,6 +97,8 @@ export * from "./components/Text/Text";
97
97
  export * from "./components/ToggleGroup/ToggleGroup";
98
98
  export * from "./components/ToggleGroup/ToggleGroup.variants";
99
99
  export * from "./components/Tooltip/Tooltip";
100
+ export * from "./components/ValueBar/SegmentedValueBar";
101
+ export * from "./components/ValueBar/SegmentedValueBarTypes";
100
102
  export * from "./components/ValueBar/ValueBar";
101
103
  export { getValueBarColorByValue } from "./components/ValueBar/ValueBarHelper";
102
104
  export * from "./components/ValueBar/ValueBarTypes";