@trackunit/react-components 1.12.13 → 1.13.0

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,8 +1,8 @@
1
1
  'use strict';
2
2
 
3
3
  var jsxRuntime = require('react/jsx-runtime');
4
- var react = require('react');
5
4
  var sharedUtils = require('@trackunit/shared-utils');
5
+ var react = require('react');
6
6
  var uiDesignTokens = require('@trackunit/ui-design-tokens');
7
7
  var uiIcons = require('@trackunit/ui-icons');
8
8
  var IconSpriteMicro = require('@trackunit/ui-icons/icons-sprite-micro.svg');
@@ -165,8 +165,17 @@ const Icon = ({ name, size = "medium", className, "data-testid": dataTestId, col
165
165
  return (jsxRuntime.jsx("span", { "aria-describedby": ariaDescribedBy, "aria-hidden": ariaHidden, "aria-label": ariaLabel ? ariaLabel : stringTs.titleCase(iconName), "aria-labelledby": ariaLabelledBy, className: cvaIcon({ color, size, fontSize, className }), "data-testid": dataTestId, id: ICON_CONTAINER_ID, onClick: onClick, ref: forwardedRef, children: jsxRuntime.jsx("svg", { "aria-labelledby": ICON_CONTAINER_ID, "data-testid": dataTestId ? `${dataTestId}-${iconName}` : iconName, role: "img", style: style, viewBox: correctViewBox, children: jsxRuntime.jsx("use", { href: href[correctIconType], ref: useTagRef }) }) }));
166
166
  };
167
167
 
168
+ /**
169
+ * The Tailwind class for minimum width of tag text.
170
+ * This is the source of truth - use parseTailwindArbitraryValue to derive the numeric value.
171
+ */
172
+ const TAG_TEXT_MIN_WIDTH_CLASS = "min-w-[80px]";
168
173
  const cvaTag = cssClassVarianceUtilities.cvaMerge([
169
- "inline-flex",
174
+ "grid",
175
+ "grid-cols-[1fr]",
176
+ "has-[[data-slot=icon]]:grid-cols-[auto_1fr]",
177
+ "has-[[data-slot=dismiss]]:grid-cols-[1fr_auto]",
178
+ "has-[[data-slot=icon]]:has-[[data-slot=dismiss]]:grid-cols-[auto_1fr_auto]",
170
179
  "justify-center",
171
180
  "items-center",
172
181
  "rounded-full",
@@ -174,11 +183,11 @@ const cvaTag = cssClassVarianceUtilities.cvaMerge([
174
183
  "px-2",
175
184
  "text-center",
176
185
  "h-min",
177
- "min-w-[1.5rem]",
178
186
  "font-medium",
179
- "truncate",
180
187
  "max-w-full",
181
188
  "w-fit",
189
+ "overflow-hidden",
190
+ "min-w-min",
182
191
  "text-xs",
183
192
  ], {
184
193
  variants: {
@@ -222,10 +231,21 @@ const cvaTag = cssClassVarianceUtilities.cvaMerge([
222
231
  color: "info",
223
232
  },
224
233
  });
225
- const cvaTagText = cssClassVarianceUtilities.cvaMerge(["truncate"]);
226
- const cvaTagIconContainer = cssClassVarianceUtilities.cvaMerge(["inline-flex", "self-center"]);
234
+ const cvaTagText = cssClassVarianceUtilities.cvaMerge(["whitespace-nowrap"], {
235
+ variants: {
236
+ truncate: {
237
+ true: ["truncate", TAG_TEXT_MIN_WIDTH_CLASS],
238
+ false: "min-w-min",
239
+ },
240
+ },
241
+ defaultVariants: {
242
+ truncate: true,
243
+ },
244
+ });
245
+ const cvaTagIconContainer = cssClassVarianceUtilities.cvaMerge(["shrink-0", "inline-flex", "self-center"]);
227
246
  const cvaTagIcon = cssClassVarianceUtilities.cvaMerge(["cursor-pointer", "transition-opacity", "hover:opacity-70", "text-neutral-500"]);
228
247
 
248
+ const TAG_TEXT_MIN_WIDTH_PX = sharedUtils.parseTailwindArbitraryValue(TAG_TEXT_MIN_WIDTH_CLASS);
229
249
  /**
230
250
  * The Tag component is used for labeling or categorizing items in the UI.
231
251
  * It's commonly used to indicate the status of an asset, mark a feature as Beta,
@@ -240,6 +260,19 @@ const cvaTagIcon = cssClassVarianceUtilities.cvaMerge(["cursor-pointer", "transi
240
260
  * @returns {ReactElement} The rendered Tag component.
241
261
  */
242
262
  const Tag = ({ className, "data-testid": dataTestId, children, size = "medium", onClose, color = "info", disabled = false, ref, icon, onMouseEnter, }) => {
263
+ const textRef = react.useRef(null);
264
+ const [shouldTruncate, setShouldTruncate] = react.useState(false);
265
+ react.useLayoutEffect(() => {
266
+ // Using simple DOM measurements to avoid the complexity of the useMeasure hook.
267
+ // Resize observers have some overhead and we don't need the full power of the useMeasure hook here.
268
+ // And tags could be rendered many at a time, multiplying the performance penalty of useMeasure.
269
+ // We measure the visible span directly using scrollWidth which gives the full content width
270
+ // even when truncation styles are applied.
271
+ if (textRef.current !== null) {
272
+ const width = textRef.current.scrollWidth;
273
+ setShouldTruncate(width >= TAG_TEXT_MIN_WIDTH_PX);
274
+ }
275
+ }, [children]);
243
276
  const isSupportedDismissColor = react.useMemo(() => {
244
277
  if (color === "neutral" || color === "primary" || color === "white" || color === "info") {
245
278
  return true;
@@ -255,11 +288,16 @@ const Tag = ({ className, "data-testid": dataTestId, children, size = "medium",
255
288
  }
256
289
  return "default";
257
290
  }, [onClose, isSupportedDismissColor, disabled, icon]);
258
- return (jsxRuntime.jsxs("div", { className: cvaTag({ className, size, color, layout, border: color === "white" ? "default" : "none" }), "data-testid": dataTestId, onMouseEnter: onMouseEnter, ref: ref, children: [icon !== null && icon !== undefined && size === "medium" ? (jsxRuntime.jsx("div", { className: cvaTagIconContainer(), children: icon })) : null, jsxRuntime.jsx("span", { className: cvaTagText(), children: children }), Boolean(onClose) && isSupportedDismissColor && size === "medium" && !disabled ? (
291
+ return (jsxRuntime.jsxs("div", { className: cvaTag({
292
+ className,
293
+ size,
294
+ color,
295
+ layout,
296
+ border: color === "white" ? "default" : "none",
297
+ }), "data-testid": dataTestId, onMouseEnter: onMouseEnter, ref: ref, children: [icon !== null && icon !== undefined && size === "medium" ? (jsxRuntime.jsx("div", { className: cvaTagIconContainer(), "data-slot": "icon", children: icon })) : null, jsxRuntime.jsx("span", { className: cvaTagText({ truncate: shouldTruncate }), ref: textRef, children: children }), Boolean(onClose) && isSupportedDismissColor && size === "medium" && !disabled ? (
259
298
  // a fix for multiselect deselecting tags working together with fade out animation
260
- jsxRuntime.jsx("div", { className: cvaTagIconContainer(), onMouseDown: onClose, children: jsxRuntime.jsx(Icon, { className: cvaTagIcon(), "data-testid": dataTestId + "Icon", name: "XCircle", size: "small", style: { WebkitTransition: "-webkit-transform 0.150s" }, type: "solid" }) })) : null] }));
299
+ jsxRuntime.jsx("div", { className: cvaTagIconContainer(), "data-slot": "dismiss", onMouseDown: onClose, children: jsxRuntime.jsx(Icon, { className: cvaTagIcon(), "data-testid": dataTestId + "Icon", name: "XCircle", size: "small", style: { WebkitTransition: "-webkit-transform 0.150s" }, type: "solid" }) })) : null] }));
261
300
  };
262
- Tag.displayName = "Tag";
263
301
 
264
302
  /**
265
303
  * A component used to display the package name and version in the Storybook docs.
@@ -1244,7 +1282,7 @@ const cvaCardHeaderContainer = cssClassVarianceUtilities.cvaMerge(["flex", "bord
1244
1282
  padding: "default",
1245
1283
  },
1246
1284
  });
1247
- const cvaCardBodyContainer$1 = cssClassVarianceUtilities.cvaMerge(["flex", "flex-grow", "overflow-auto"], {
1285
+ const cvaCardBodyContainer = cssClassVarianceUtilities.cvaMerge(["flex", "flex-grow", "overflow-auto"], {
1248
1286
  variants: {
1249
1287
  direction: {
1250
1288
  row: "flex-row",
@@ -1289,7 +1327,7 @@ const Card = react.forwardRef(function Card({ children, onClick, fullHeight = fa
1289
1327
  * @returns {ReactElement} CardBody component
1290
1328
  */
1291
1329
  const CardBody = ({ children, "data-testid": dataTestId, className, direction = "column", gap = "default", padding = "default", id, }) => {
1292
- return (jsxRuntime.jsx("div", { className: cvaCardBodyContainer$1({
1330
+ return (jsxRuntime.jsx("div", { className: cvaCardBodyContainer({
1293
1331
  gap,
1294
1332
  padding,
1295
1333
  className,
@@ -2372,7 +2410,7 @@ const useScrollDetection = (options) => {
2372
2410
  }), [ref, element, scrollState]);
2373
2411
  };
2374
2412
 
2375
- const cvaZStackContainer = cssClassVarianceUtilities.cvaMerge(["grid", "grid-cols-1", "grid-rows-1"]);
2413
+ const cvaZStackContainer = cssClassVarianceUtilities.cvaMerge(["grid", "grid-cols-1", "grid-rows-1", "isolate"]);
2376
2414
  const cvaZStackItem = cssClassVarianceUtilities.cvaMerge(["col-start-1", "col-end-1", "row-start-1", "row-end-2"]);
2377
2415
 
2378
2416
  /**
@@ -3124,63 +3162,163 @@ const cvaKPI = cssClassVarianceUtilities.cvaMerge(["w-full", "flex", "flex-col"]
3124
3162
  variant: "default",
3125
3163
  },
3126
3164
  });
3127
- const cvaKPIHeader = cssClassVarianceUtilities.cvaMerge(["flex", "flex-row", "justify-between", "gap-1"]);
3128
- const cvaKPITitleText = cssClassVarianceUtilities.cvaMerge(["truncate", "whitespace-nowrap"]);
3129
- const cvaKPIvalueText = cssClassVarianceUtilities.cvaMerge(["truncate", "whitespace-nowrap", "text-lg", "font-medium"], {
3165
+ const cvaKPITrendPercentage = cssClassVarianceUtilities.cvaMerge([""], {
3130
3166
  variants: {
3131
- variant: {
3132
- small: ["mt-0.5"],
3133
- condensed: [""],
3134
- default: [""],
3167
+ color: {
3168
+ success: ["text-success-600"],
3169
+ danger: ["text-danger-600"],
3170
+ warning: ["text-warning-600"],
3171
+ neutral: ["text-neutral-600"],
3172
+ info: ["text-info-600"],
3135
3173
  },
3136
3174
  },
3137
3175
  defaultVariants: {
3138
- variant: "default",
3176
+ color: "success",
3139
3177
  },
3140
3178
  });
3141
- const cvaKPITrendPercentage = cssClassVarianceUtilities.cvaMerge([""], {
3179
+
3180
+ /**
3181
+ * The KPI component is used to display KPIs.
3182
+ *
3183
+ * @param {KPIProps} props - The props for the KPI component
3184
+ * @returns {ReactElement} KPI component
3185
+ */
3186
+ const KPI = ({ title, value, loading = false, unit, className, "data-testid": dataTestId, tooltipLabel, variant = "default", style, ...rest }) => {
3187
+ const isSmallVariant = variant === "small";
3188
+ return (jsxRuntime.jsx(Tooltip, { className: "min-w-8 shrink-0", "data-testid": dataTestId ? `${dataTestId}-tooltip` : undefined, disabled: tooltipLabel === undefined || tooltipLabel === "", label: tooltipLabel, placement: "bottom", children: jsxRuntime.jsxs("div", { className: cvaKPI({ variant, className }), "data-testid": dataTestId, style: style, ...rest, children: [loading ? (jsxRuntime.jsx(SkeletonLines, { className: tailwindMerge.twMerge("flex", "items-center", "flex-row", isSmallVariant ? "h-4" : "h-5"), "data-testid": dataTestId ? `${dataTestId}-title-loading` : undefined, height: isSmallVariant ? uiDesignTokens.themeFontSize.xs : uiDesignTokens.themeFontSize.sm, lines: 1, width: "80%" })) : (jsxRuntime.jsx(Text, { className: tailwindMerge.twMerge("truncate", "whitespace-nowrap"), "data-testid": dataTestId ? `${dataTestId}-title` : undefined, size: isSmallVariant ? "small" : "medium", subtle: true, weight: isSmallVariant ? "normal" : "bold", children: title })), jsxRuntime.jsx("div", { className: tailwindMerge.twMerge("truncate", "whitespace-nowrap"), children: loading ? (jsxRuntime.jsx(SkeletonLines, { className: "flex h-7 flex-row items-center", "data-testid": dataTestId ? `${dataTestId}-value-loading` : undefined, height: uiDesignTokens.themeFontSize.base, lines: 1, width: "100%" })) : (jsxRuntime.jsxs(Text, { className: "truncate whitespace-nowrap text-lg font-medium", "data-testid": dataTestId ? `${dataTestId}-value` : undefined, size: isSmallVariant ? "small" : "large", type: "div", weight: isSmallVariant ? "bold" : "thick", children: [value, " ", unit] })) })] }) }));
3189
+ };
3190
+
3191
+ /**
3192
+ * The TrendIndicator component is used within the KPI Card component to display the trend indicator.
3193
+ *
3194
+ * @param {TrendIndicatorProps} props - The props for the TrendIndicator component
3195
+ * @returns {ReactElement} TrendIndicator component
3196
+ */
3197
+ const TrendIndicator = ({ value, trend, label, icon = undefined, color = undefined, "data-testid": dataTestId, className, }) => {
3198
+ return (jsxRuntime.jsxs("div", { className: tailwindMerge.twMerge("flex flex-row items-center gap-1", className), "data-testid": dataTestId, children: [value !== undefined ? (jsxRuntime.jsx(Text, { "data-testid": dataTestId ? `${dataTestId}-value` : undefined, size: "small", weight: "normal", children: value })) : null, jsxRuntime.jsxs("div", { className: "flex items-center", children: [icon ? (jsxRuntime.jsx(Icon, { color: color, "data-testid": dataTestId ? `${dataTestId}-icon` : undefined, name: icon, size: "small" })) : null, jsxRuntime.jsx(Text, { className: cvaKPITrendPercentage({ color }), "data-testid": dataTestId ? `${dataTestId}-trend` : undefined, size: "small", weight: "bold", children: trend })] }), jsxRuntime.jsx(Text, { "data-testid": dataTestId ? `${dataTestId}-label` : undefined, size: "small", weight: "normal", children: label })] }));
3199
+ };
3200
+
3201
+ /**
3202
+ * The TrendIndicators component is used within the KPI Card component to display the trend indicators.
3203
+ *
3204
+ * @param {TrendIndicatorsProps} props - The props for the TrendIndicators component
3205
+ * @returns {ReactElement} TrendIndicators component
3206
+ */
3207
+ const TrendIndicators = ({ trends, "data-testid": dataTestId, className, }) => {
3208
+ return (jsxRuntime.jsx("span", { className: tailwindMerge.twMerge("flex flex-row items-center gap-1", className), "data-testid": dataTestId, children: trends.map((trend, index) => (jsxRuntime.jsx(TrendIndicator, { "data-testid": dataTestId ? `${dataTestId}-trend-indicator-${index}` : undefined, ...trend }, index))) }));
3209
+ };
3210
+
3211
+ const cvaValueBar = cssClassVarianceUtilities.cvaMerge([
3212
+ "w-full",
3213
+ "overflow-hidden",
3214
+ "rounded",
3215
+ "bg-neutral-100",
3216
+ "appearance-none",
3217
+ "[&::-webkit-progress-bar]:bg-transparent",
3218
+ "[&::-webkit-progress-value]:bg-current",
3219
+ "[&::-moz-progress-bar]:bg-current",
3220
+ ], {
3142
3221
  variants: {
3143
- color: {
3144
- success: ["text-success-500"],
3145
- danger: ["text-danger-500"],
3146
- neutral: ["text-neutral-500"],
3222
+ size: {
3223
+ extraSmall: "h-1",
3224
+ small: "h-3",
3225
+ large: "h-9",
3147
3226
  },
3148
3227
  },
3149
3228
  defaultVariants: {
3150
- color: "success",
3229
+ size: "small",
3151
3230
  },
3152
3231
  });
3153
- const cvaKPIValueContainer = cssClassVarianceUtilities.cvaMerge(["truncate", "whitespace-nowrap"], {
3232
+ const cvaValueBarText = cssClassVarianceUtilities.cvaMerge(["whitespace-nowrap"], {
3154
3233
  variants: {
3155
- isDefaultAndHasTrendValue: {
3156
- true: [],
3157
- false: ["flex", "flex-row", "items-center", "gap-1"],
3234
+ size: {
3235
+ small: "leading-xs text-xs font-medium text-neutral-600",
3236
+ large: "absolute pl-3 text-base text-white drop-shadow-lg",
3158
3237
  },
3159
3238
  },
3160
3239
  defaultVariants: {
3161
- isDefaultAndHasTrendValue: true,
3240
+ size: "small",
3162
3241
  },
3163
3242
  });
3164
3243
 
3165
- const LoadingContent$1 = () => (jsxRuntime.jsx("div", { className: "flex h-11 flex-row items-center gap-3", "data-testid": "kpi-card-loading-content", children: jsxRuntime.jsx("div", { className: "w-full", children: jsxRuntime.jsx(SkeletonLines, { gap: 3, height: [14, 18], lines: 2, width: [80, 60] }) }) }));
3166
3244
  /**
3167
- * The KPI component is used to display KPIs.
3168
- *
3169
- * @param {KPIProps} props - The props for the KPI component
3170
- * @returns {ReactElement} KPI component
3245
+ * Helper function to get normalized score in range 0-1 used by the ValueBar
3246
+
3247
+ * @param {number} value - value for which score should be returned
3248
+ * @param {number} min - min range value
3249
+ * @param {number} max - max range value
3250
+ * @param {boolean} zeroScoreAllowed - If true, allows the score to be exactly 0 when the value is less than or equal to the minimum.
3251
+ * If false or not provided, ensures a minimal score (0.01) is returned to always render a small fragment.
3252
+ * @returns {number} normalized score
3171
3253
  */
3172
- const KPI = ({ title, value, loading = false, unit, className, "data-testid": dataTestId, tooltipLabel, variant = "default", trend, style, ...rest }) => {
3173
- const isSmallVariant = variant === "small";
3174
- return (jsxRuntime.jsx(Tooltip, { "data-testid": dataTestId ? `${dataTestId}-tooltip` : undefined, disabled: tooltipLabel === undefined || tooltipLabel === "", label: tooltipLabel, placement: "bottom", style: style, children: jsxRuntime.jsx("div", { className: cvaKPI({ variant, className }), "data-testid": dataTestId ? `${dataTestId}` : undefined, ...rest, children: loading ? (jsxRuntime.jsx(LoadingContent$1, {})) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("div", { className: cvaKPIHeader(), children: jsxRuntime.jsx(Text, { className: cvaKPITitleText(), "data-testid": dataTestId ? `${dataTestId}-title` : undefined, size: isSmallVariant ? "small" : "medium", subtle: true, weight: isSmallVariant ? "normal" : "bold", children: title }) }), jsxRuntime.jsx(Text, { className: cvaKPIvalueText({ variant }), "data-testid": dataTestId ? `${dataTestId}-value` : undefined, size: isSmallVariant ? "small" : "large", type: "div", weight: isSmallVariant ? "bold" : "thick", children: jsxRuntime.jsxs("div", { className: cvaKPIValueContainer({
3175
- isDefaultAndHasTrendValue: Boolean(trend !== undefined && trend.value !== undefined && !isSmallVariant),
3176
- className,
3177
- }), children: [jsxRuntime.jsxs("span", { className: cvaKPIvalueText({ variant }), children: [value, " ", unit] }), jsxRuntime.jsx(TrendIndicator, { isSmallVariant: isSmallVariant, trend: trend, unit: unit })] }) })] })) }) }));
3254
+ const getScore = (value, min, max, zeroScoreAllowed) => {
3255
+ if (value <= min) {
3256
+ if (zeroScoreAllowed === true) {
3257
+ return 0;
3258
+ }
3259
+ return 0.01; // always render at least some small fragment
3260
+ }
3261
+ if (value >= max) {
3262
+ return 1;
3263
+ }
3264
+ return (value - min) / (max - min);
3178
3265
  };
3179
- const TrendIndicator = ({ trend, unit, isSmallVariant }) => {
3180
- if (!trend) {
3181
- return null;
3266
+ /**
3267
+ * Helper function to get default color used by the ValueBar
3268
+
3269
+ * @param {number} score - score number for which color should be returned
3270
+ * @returns {string} color value
3271
+ */
3272
+ const getDefaultFillColor = (score) => {
3273
+ if (score < 0.3) {
3274
+ return uiDesignTokens.color("DANGER", 500, "CSS");
3275
+ }
3276
+ if (score >= 0.6) {
3277
+ return uiDesignTokens.color("SUCCESS", 600, "CSS");
3278
+ }
3279
+ return uiDesignTokens.color("WARNING", 300, "CSS");
3280
+ };
3281
+ /**
3282
+ * Helper function to get custom color used by the ValueBar
3283
+
3284
+ * @param {number} score - score number for which color should be returned
3285
+ * @param {LevelColors} levelColors - custom colors and levels definitions
3286
+ * @returns {string} color value
3287
+ */
3288
+ const getFillColor = (score, levelColors) => {
3289
+ if (levelColors.low !== undefined && score < (levelColors.low.level !== undefined ? levelColors.low.level : 0)) {
3290
+ return levelColors.low.color;
3291
+ }
3292
+ if (levelColors.high !== undefined && score >= (levelColors.high.level !== undefined ? levelColors.high.level : 0)) {
3293
+ return levelColors.high.color;
3182
3294
  }
3183
- return (jsxRuntime.jsxs("div", { className: "flex flex-row items-center gap-1", "data-testid": "trend-indicator", children: [!isSmallVariant && trend.value !== undefined ? (jsxRuntime.jsxs(Text, { "data-testid": "trend-value", size: "small", weight: "normal", children: [trend.value, " ", unit] })) : null, trend.variant !== undefined && trend.variant.icon !== undefined ? (jsxRuntime.jsx(Icon, { color: trend.variant.color, name: trend.variant.icon, size: "small" })) : null, trend.percentage !== undefined ? (jsxRuntime.jsxs(Text, { className: cvaKPITrendPercentage({ color: trend.variant?.color }), size: "small", weight: "bold", children: [trend.percentage, "%"] })) : null] }));
3295
+ return levelColors.medium?.color ?? uiDesignTokens.color("WARNING", 300);
3296
+ };
3297
+ /**
3298
+ * Helper function to get color used by the ValueBar
3299
+
3300
+ * @param {number} value - value for which color should be returned
3301
+ * @param {number} min - min range value
3302
+ * @param {number} max - max range value
3303
+ * @param {LevelColors} levelColors - level colors used to coloring the bar
3304
+ * @returns {ReactElement} ValueBar component
3305
+ */
3306
+ const getValueBarColorByValue = (value, min, max, levelColors) => {
3307
+ const score = getScore(value, min, max);
3308
+ return getFillColor(score, levelColors);
3309
+ };
3310
+
3311
+ /**
3312
+ * ValueBar component is used to display value on a colorful bar within provided range.
3313
+
3314
+ * @param {ValueBarProps} props - The props for the ValueBar component
3315
+ * @returns {ReactElement} ValueBar component
3316
+ */
3317
+ const ValueBar = ({ value, min = 0, max = 100, unit, size = "small", levelColors, valueColor, showValue = false, className, "data-testid": dataTestId, zeroScoreAllowed = false, }) => {
3318
+ const score = getScore(value, min, max, zeroScoreAllowed);
3319
+ const barFillColor = levelColors ? getFillColor(score, levelColors) : getDefaultFillColor(score);
3320
+ const valueText = `${Number(value.toFixed(1))}${sharedUtils.nonNullable(unit) ? unit : ""}`;
3321
+ return (jsxRuntime.jsxs("span", { className: "relative flex items-center gap-2", "data-testid": dataTestId, children: [jsxRuntime.jsx("progress", { "aria-label": valueText, className: cvaValueBar({ className, size }), max: 100, style: { color: barFillColor }, value: score * 100 }), showValue && (size === "small" || size === "large") ? (jsxRuntime.jsx(Text, { className: cvaValueBarText({ size }), "data-testid": dataTestId ? `${dataTestId}-value` : undefined, children: jsxRuntime.jsx("span", { style: valueColor ? { color: valueColor } : undefined, children: valueText }) })) : null] }));
3184
3322
  };
3185
3323
 
3186
3324
  const cvaKPICard = cssClassVarianceUtilities.cvaMerge([
@@ -3214,14 +3352,6 @@ const cvaKPIIconContainer = cssClassVarianceUtilities.cvaMerge(["flex", "items-c
3214
3352
  },
3215
3353
  },
3216
3354
  });
3217
- const cvaCardBodyContainer = cssClassVarianceUtilities.cvaMerge(["grid", "grid-cols-[1fr_auto]", "px-3", "pb-2", "pt-3"], {
3218
- variants: {
3219
- iconName: {
3220
- true: "gap-2",
3221
- false: "gap-3 ",
3222
- },
3223
- },
3224
- });
3225
3355
 
3226
3356
  /**
3227
3357
  * The KPICard component is used to display KPIs.
@@ -3229,13 +3359,15 @@ const cvaCardBodyContainer = cssClassVarianceUtilities.cvaMerge(["grid", "grid-c
3229
3359
  * @param {KPICardProps} props - The props for the KPICard component
3230
3360
  * @returns {ReactElement} KPICard component
3231
3361
  */
3232
- const KPICard = ({ isActive = false, onClick, className, "data-testid": dataTestId, children, iconName = undefined, iconColor = "info", loading = false, ...rest }) => {
3362
+ const KPICard = ({ isActive = false, onClick, className, "data-testid": dataTestId, children, iconName = undefined, iconColor = "info", loading = false, notice, valueBar, trends, unit, ...rest }) => {
3233
3363
  const isClickable = Boolean(onClick !== undefined && loading !== true);
3234
3364
  return (jsxRuntime.jsx(Card, { className: cvaKPICard({
3235
3365
  isClickable,
3236
3366
  isActive,
3237
3367
  className,
3238
- }), "data-testid": dataTestId ? dataTestId : undefined, onClick: onClick, children: jsxRuntime.jsxs(CardBody, { className: cvaCardBodyContainer({ iconName: Boolean(iconName) }), gap: "none", padding: "none", children: [jsxRuntime.jsx(KPI, { ...rest, className: "p-0", "data-testid": dataTestId ? `${dataTestId}-kpi` : undefined, loading: loading }), iconName ? (jsxRuntime.jsx("div", { className: cvaKPIIconContainer({ iconColor }), children: jsxRuntime.jsx(Icon, { name: iconName, size: "small", type: "solid" }) })) : null, children] }) }));
3368
+ }), "data-testid": dataTestId ? dataTestId : undefined, onClick: onClick, children: jsxRuntime.jsxs(CardBody, { className: "grid gap-2 px-3 pb-2 pt-3", gap: "none", padding: "none", children: [jsxRuntime.jsxs("div", { className: "grid grid-cols-[1fr_auto] justify-between gap-2", children: [jsxRuntime.jsx(KPI, { ...rest, className: "p-0", "data-testid": dataTestId ? `${dataTestId}-kpi` : undefined, loading: loading, unit: unit }), iconName ? (jsxRuntime.jsx("div", { className: cvaKPIIconContainer({ iconColor }), children: jsxRuntime.jsx(Icon, { name: iconName, size: "small", type: "solid" }) })) : null] }), trends !== undefined && trends.length > 0 ? (loading ? (jsxRuntime.jsx(SkeletonLines, { className: "h-4", "data-testid": dataTestId ? `${dataTestId}-trend-indicator-loading` : undefined, height: uiDesignTokens.themeFontSize.xs, lines: 1, width: "100%" })) : (jsxRuntime.jsx(TrendIndicators, { "data-testid": dataTestId ? `${dataTestId}-trend-indicators` : undefined, trends: trends }))) : null, valueBar !== undefined ? (loading ? (jsxRuntime.jsx(SkeletonLines, { className: "h-4", "data-testid": dataTestId ? `${dataTestId}-value-bar-loading` : undefined, gap: 0, height: uiDesignTokens.themeFontSize.xs, lines: 1, width: "100%" })) : (jsxRuntime.jsx(ValueBar, { className: "h-2", "data-testid": dataTestId ? `${dataTestId}-value-bar` : undefined, ...valueBar }))) : null, notice !== undefined ? (loading ? (jsxRuntime.jsx(SkeletonLines, { className: "h-4", "data-testid": dataTestId ? `${dataTestId}-notice-loading` : undefined, gap: 0, height: uiDesignTokens.themeFontSize.xs, lines: 1, width: "100%" })) : (
3369
+ // NOTE: Can't use Notice component here due to the non-flexible text styling options
3370
+ jsxRuntime.jsxs("div", { className: "flex items-center gap-1 truncate", "data-testid": dataTestId ? `${dataTestId}-notice` : undefined, children: [notice.iconName ? (jsxRuntime.jsx(Icon, { color: notice.iconColor, "data-testid": dataTestId ? `${dataTestId}-notice-icon` : undefined, name: notice.iconName, size: "small" })) : null, jsxRuntime.jsx(Text, { className: "truncate text-neutral-900", "data-testid": dataTestId ? `${dataTestId}-notice-label` : undefined, size: "small", children: notice.label })] }))) : null, children] }) }));
3239
3371
  };
3240
3372
 
3241
3373
  const cvaListContainer = cssClassVarianceUtilities.cvaMerge(["overflow-y-auto", "overflow-x-hidden", "h-full"], {
@@ -3371,6 +3503,7 @@ const ListLoadingIndicator = ({ type, hasThumbnail, thumbnailShape, hasDescripti
3371
3503
  *
3372
3504
  * @example Basic usage
3373
3505
  * ```tsx
3506
+ * import { useList, List } from "@trackunit/react-components";
3374
3507
  * const list = useList({
3375
3508
  * count: items.length,
3376
3509
  * getItem: index => items[index],
@@ -3387,6 +3520,7 @@ const ListLoadingIndicator = ({ type, hasThumbnail, thumbnailShape, hasDescripti
3387
3520
  * ```
3388
3521
  * @example With header
3389
3522
  * ```tsx
3523
+ * import { useList, List } from "@trackunit/react-components";
3390
3524
  * const list = useList({
3391
3525
  * count: items.length,
3392
3526
  * getItem: index => items[index],
@@ -3895,7 +4029,11 @@ const cvaMenuList = cssClassVarianceUtilities.cvaMerge([
3895
4029
  "overflow-y-auto",
3896
4030
  ]);
3897
4031
  const cvaMenuListDivider = cssClassVarianceUtilities.cvaMerge(["mx-[-4px]", "my-0.5", "min-h-px", "bg-neutral-300"]);
3898
- const cvaMenuListMultiSelect = cssClassVarianceUtilities.cvaMerge("hover:!bg-blue-200");
4032
+ const cvaMenuListMultiSelect = cssClassVarianceUtilities.cvaMerge([
4033
+ "!has-[:checked]:bg-primary-100",
4034
+ "!hover:has-[:checked]:bg-primary-200",
4035
+ "!focus-within:has-[:checked]:bg-primary-100",
4036
+ ]);
3899
4037
  const cvaMenuListItem = cssClassVarianceUtilities.cvaMerge("max-w-full");
3900
4038
 
3901
4039
  /**
@@ -3986,7 +4124,7 @@ const cvaMenuItemPrefix = cssClassVarianceUtilities.cvaMerge([
3986
4124
  ], {
3987
4125
  variants: {
3988
4126
  selected: {
3989
- true: "text-neutral-600",
4127
+ true: "text-primary-600",
3990
4128
  false: "",
3991
4129
  },
3992
4130
  variant: {
@@ -4101,7 +4239,7 @@ const MenuList = ({ "data-testid": dataTestId, className, children, isMulti = fa
4101
4239
  : cvaMenuListItem({ className: menuItem.props.className }),
4102
4240
  selected: isSelected,
4103
4241
  suffix: menuItem.props.suffix ??
4104
- (isMulti && isSelected ? jsxRuntime.jsx(Icon, { className: "block text-blue-600", name: "Check", size: "medium" }) : null),
4242
+ (isMulti && isSelected ? jsxRuntime.jsx(Icon, { className: "text-primary-600 block", name: "Check", size: "medium" }) : null),
4105
4243
  });
4106
4244
  }
4107
4245
  return null;
@@ -4129,8 +4267,8 @@ const MoreMenu = ({ className, "data-testid": dataTestId, popoverProps, iconProp
4129
4267
  return (jsxRuntime.jsx("div", { className: cvaMoreMenu({ className }), "data-testid": dataTestId ? dataTestId : undefined, ref: actionMenuRef, children: jsxRuntime.jsxs(Popover, { placement: "bottom-end", ...popoverProps, children: [jsxRuntime.jsx(PopoverTrigger, { children: customButton ?? (jsxRuntime.jsx(IconButton, { "data-testid": "more-menu-icon", ...iconButtonProps, icon: jsxRuntime.jsx(Icon, { name: "EllipsisHorizontal", ...iconProps }) })) }), jsxRuntime.jsx(PopoverContent, { portalId: customPortalId, children: close => (typeof children === "function" ? children(close) : children) })] }) }));
4130
4268
  };
4131
4269
 
4132
- const cvaNotice = cssClassVarianceUtilities.cvaMerge(["flex", "items-center"]);
4133
- const cvaNoticeLabel = cssClassVarianceUtilities.cvaMerge(["pl-1", "pr-1", "font-medium", "text-sm"], {
4270
+ const cvaNotice = cssClassVarianceUtilities.cvaMerge(["flex", "items-center", "gap-1"]);
4271
+ const cvaNoticeLabel = cssClassVarianceUtilities.cvaMerge(["font-medium", "text-sm", "overflow-hidden", "text-ellipsis"], {
4134
4272
  variants: {
4135
4273
  color: {
4136
4274
  neutral: "text-neutral-400",
@@ -4170,12 +4308,12 @@ const cvaNoticeIcon = cssClassVarianceUtilities.cvaMerge(["rounded-full", "items
4170
4308
  * _**Do use** notices to communicate non-essential information that does not necessarily require action to be taken._
4171
4309
  *
4172
4310
  * _**Do not use** notices for essential information (use `<Alert/>` instead), or to communicate information related to the state of an asset (use `<Indicator/>` instead)._
4173
-
4311
+ *
4174
4312
  * @param {NoticeProps} props - The props for the Notice component
4175
4313
  * @returns {ReactElement} Notice component
4176
4314
  */
4177
- const Notice = ({ "data-testid": dataTestId, icon, label, color = "neutral", withLabel = true, className, tooltipLabel = label, withTooltip = false, size = "medium", ...rest }) => {
4178
- return (jsxRuntime.jsx(Tooltip, { className: className, disabled: withTooltip === false, label: tooltipLabel, placement: "bottom", children: jsxRuntime.jsxs("div", { "aria-label": label, className: cvaNotice(), "data-testid": dataTestId, ...rest, children: [jsxRuntime.jsx("div", { className: cvaNoticeIcon({ color }), "data-testid": dataTestId ? `${dataTestId}-icon` : "notice-icon", children: icon }), label && withLabel ? (jsxRuntime.jsx("div", { className: cvaNoticeLabel({ color, size }), "data-testid": dataTestId ? `${dataTestId}-label` : "notice-label", children: label })) : null] }) }));
4315
+ const Notice = ({ "data-testid": dataTestId, iconName = undefined, iconSize = "medium", iconColor = undefined, label, color = "neutral", withLabel = true, className, tooltipLabel = label, withTooltip = false, size = "medium", ...rest }) => {
4316
+ return (jsxRuntime.jsx(Tooltip, { className: className, disabled: withTooltip === false, label: tooltipLabel, placement: "bottom", children: jsxRuntime.jsxs("div", { "aria-label": label, className: cvaNotice(), "data-testid": dataTestId, ...rest, children: [sharedUtils.nonNullable(iconName) ? (jsxRuntime.jsx("div", { className: cvaNoticeIcon({ color: iconColor || color }), "data-testid": dataTestId ? `${dataTestId}-icon` : "notice-icon", children: jsxRuntime.jsx(Icon, { name: iconName, size: iconSize }) })) : null, label && withLabel ? (jsxRuntime.jsx("div", { className: cvaNoticeLabel({ color, size }), "data-testid": dataTestId ? `${dataTestId}-label` : "notice-label", children: label })) : null] }) }));
4179
4317
  };
4180
4318
 
4181
4319
  const cvaPage = cssClassVarianceUtilities.cvaMerge(["grid", "h-full"], {
@@ -4565,7 +4703,18 @@ const cvaSpacer = cssClassVarianceUtilities.cvaMerge([], {
4565
4703
 
4566
4704
  /**
4567
4705
  * The Spacer component is used for adding a bit of space in the ui.
4568
-
4706
+ *
4707
+ * @example basic usage
4708
+ * ```tsx
4709
+ * import { Spacer } from "@trackunit/react-components";
4710
+ * const MySpacer = () => {
4711
+ * return (
4712
+ * <div>
4713
+ * <Spacer size="small" border data-testid="my-spacer-testid" />
4714
+ * </div>
4715
+ * );
4716
+ * };
4717
+ * ```
4569
4718
  * @param {SpacerProps} props - The props for the Spacer component
4570
4719
  * @returns {ReactElement} Spacer component
4571
4720
  */
@@ -4995,119 +5144,6 @@ const ToggleButton = ({ title, size, children, "data-testid": dataTestId, classN
4995
5144
  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] })) }));
4996
5145
  };
4997
5146
 
4998
- const cvaValueBar = cssClassVarianceUtilities.cvaMerge([
4999
- "w-full",
5000
- "overflow-hidden",
5001
- "rounded",
5002
- "bg-neutral-100",
5003
- "appearance-none",
5004
- "[&::-webkit-progress-bar]:bg-transparent",
5005
- "[&::-webkit-progress-value]:bg-current",
5006
- "[&::-moz-progress-bar]:bg-current",
5007
- ], {
5008
- variants: {
5009
- size: {
5010
- extraSmall: "h-1",
5011
- small: "h-3",
5012
- large: "h-9",
5013
- },
5014
- },
5015
- defaultVariants: {
5016
- size: "small",
5017
- },
5018
- });
5019
- const cvaValueBarText = cssClassVarianceUtilities.cvaMerge(["whitespace-nowrap"], {
5020
- variants: {
5021
- size: {
5022
- small: "text-sm font-bold text-neutral-400",
5023
- large: "absolute pl-3 text-base text-white drop-shadow-lg",
5024
- },
5025
- },
5026
- defaultVariants: {
5027
- size: "small",
5028
- },
5029
- });
5030
-
5031
- /**
5032
- * Helper function to get normalized score in range 0-1 used by the ValueBar
5033
-
5034
- * @param {number} value - value for which score should be returned
5035
- * @param {number} min - min range value
5036
- * @param {number} max - max range value
5037
- * @param {boolean} zeroScoreAllowed - If true, allows the score to be exactly 0 when the value is less than or equal to the minimum.
5038
- * If false or not provided, ensures a minimal score (0.01) is returned to always render a small fragment.
5039
- * @returns {number} normalized score
5040
- */
5041
- const getScore = (value, min, max, zeroScoreAllowed) => {
5042
- if (value <= min) {
5043
- if (zeroScoreAllowed === true) {
5044
- return 0;
5045
- }
5046
- return 0.01; // always render at least some small fragment
5047
- }
5048
- if (value >= max) {
5049
- return 1;
5050
- }
5051
- return (value - min) / (max - min);
5052
- };
5053
- /**
5054
- * Helper function to get default color used by the ValueBar
5055
-
5056
- * @param {number} score - score number for which color should be returned
5057
- * @returns {string} color value
5058
- */
5059
- const getDefaultFillColor = (score) => {
5060
- if (score < 0.3) {
5061
- return uiDesignTokens.color("DANGER", 500, "CSS");
5062
- }
5063
- if (score >= 0.6) {
5064
- return uiDesignTokens.color("SUCCESS", 600, "CSS");
5065
- }
5066
- return uiDesignTokens.color("WARNING", 300, "CSS");
5067
- };
5068
- /**
5069
- * Helper function to get custom color used by the ValueBar
5070
-
5071
- * @param {number} score - score number for which color should be returned
5072
- * @param {LevelColors} levelColors - custom colors and levels definitions
5073
- * @returns {string} color value
5074
- */
5075
- const getFillColor = (score, levelColors) => {
5076
- if (levelColors.low !== undefined && score < (levelColors.low.level !== undefined ? levelColors.low.level : 0)) {
5077
- return levelColors.low.color;
5078
- }
5079
- if (levelColors.high !== undefined && score >= (levelColors.high.level !== undefined ? levelColors.high.level : 0)) {
5080
- return levelColors.high.color;
5081
- }
5082
- return levelColors.medium?.color ?? uiDesignTokens.color("WARNING", 300);
5083
- };
5084
- /**
5085
- * Helper function to get color used by the ValueBar
5086
-
5087
- * @param {number} value - value for which color should be returned
5088
- * @param {number} min - min range value
5089
- * @param {number} max - max range value
5090
- * @param {LevelColors} levelColors - level colors used to coloring the bar
5091
- * @returns {ReactElement} ValueBar component
5092
- */
5093
- const getValueBarColorByValue = (value, min, max, levelColors) => {
5094
- const score = getScore(value, min, max);
5095
- return getFillColor(score, levelColors);
5096
- };
5097
-
5098
- /**
5099
- * ValueBar component is used to display value on a colorful bar within provided range.
5100
-
5101
- * @param {ValueBarProps} props - The props for the ValueBar component
5102
- * @returns {ReactElement} ValueBar component
5103
- */
5104
- const ValueBar = ({ value, min = 0, max = 100, unit, size = "small", levelColors, valueColor, showValue = false, className, "data-testid": dataTestId, zeroScoreAllowed = false, }) => {
5105
- const score = getScore(value, min, max, zeroScoreAllowed);
5106
- const barFillColor = levelColors ? getFillColor(score, levelColors) : getDefaultFillColor(score);
5107
- const valueText = `${Number(value.toFixed(1))}${sharedUtils.nonNullable(unit) ? unit : ""}`;
5108
- return (jsxRuntime.jsxs("span", { className: "relative flex items-center gap-2", "data-testid": dataTestId, children: [jsxRuntime.jsx("progress", { "aria-label": valueText, className: cvaValueBar({ className, size }), max: 100, style: { color: barFillColor }, value: score * 100 }), showValue && (size === "small" || size === "large") ? (jsxRuntime.jsx(Text, { className: cvaValueBarText({ size }), "data-testid": dataTestId ? `${dataTestId}-value` : undefined, children: jsxRuntime.jsx("span", { style: valueColor ? { color: valueColor } : undefined, children: valueText }) })) : null] }));
5109
- };
5110
-
5111
5147
  /**
5112
5148
  * Base64URL encode bytes to a URL-safe string
5113
5149
  */
@@ -6111,6 +6147,8 @@ exports.Tag = Tag;
6111
6147
  exports.Text = Text;
6112
6148
  exports.ToggleGroup = ToggleGroup;
6113
6149
  exports.Tooltip = Tooltip;
6150
+ exports.TrendIndicator = TrendIndicator;
6151
+ exports.TrendIndicators = TrendIndicators;
6114
6152
  exports.ValueBar = ValueBar;
6115
6153
  exports.ZStack = ZStack;
6116
6154
  exports.cvaButton = cvaButton;