@trackunit/react-components 1.14.5 → 1.14.11

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.esm.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { jsx, jsxs, Fragment as Fragment$1 } from 'react/jsx-runtime';
2
2
  import { objectKeys, uuidv4, parseTailwindArbitraryValue, objectEntries, nonNullable, objectValues, filterByMultiple } from '@trackunit/shared-utils';
3
3
  import { useRef, useMemo, useEffect, useState, useLayoutEffect, useCallback, createElement, forwardRef, Fragment, memo, useId, useReducer, Children, isValidElement, cloneElement, createContext, useContext } from 'react';
4
- import { intentPalette, generalPalette, criticalityPalette, activityPalette, utilizationPalette, sitesPalette, rentalStatusPalette, themeScreenSizeAsNumber, themeContainerSize, themeFontSize, color } from '@trackunit/ui-design-tokens';
4
+ import { intentPalette, generalPalette, criticalityPalette, activityPalette, utilizationPalette, sitesPalette, rentalStatusPalette, themeScreenSizeAsNumber, themeContainerSize, color } from '@trackunit/ui-design-tokens';
5
5
  import { iconNames } from '@trackunit/ui-icons';
6
6
  import IconSpriteMicro from '@trackunit/ui-icons/icons-sprite-micro.svg';
7
7
  import IconSpriteMini from '@trackunit/ui-icons/icons-sprite-mini.svg';
@@ -1832,21 +1832,74 @@ const DetailsList = ({ details, className, hasLink = false }) => {
1832
1832
  return (jsx("div", { className: cvaDetailsList({ className, hasLink }), children: details.map((value, index, array) => (jsxs(Fragment, { children: [jsx("span", { className: cvaDetailsListItem({ className }), children: value }), index < array.length - 1 && (jsx("div", { className: "mx-0.5 flex items-center", children: jsx(Icon, { className: "w-4 text-neutral-300", color: "neutral", name: "Slash", size: "small" }) }))] }, index))) }));
1833
1833
  };
1834
1834
 
1835
+ const VALID_SIZE_KEYS = [
1836
+ "xs",
1837
+ "sm",
1838
+ "base",
1839
+ "lg",
1840
+ "xl",
1841
+ "2xl",
1842
+ "3xl",
1843
+ "4xl",
1844
+ "5xl",
1845
+ "6xl",
1846
+ "7xl",
1847
+ "8xl",
1848
+ "9xl",
1849
+ ];
1835
1850
  /**
1836
- * Generates a random width percentage string for skeleton loading components.
1851
+ * Extract the size key from a text size string (e.g., "text-base" "base").
1837
1852
  *
1838
- * @param {object} params - The parameter object
1839
- * @param {number} params.min - Minimum percentage value (e.g., 30 for 30%)
1840
- * @param {number} params.max - Maximum percentage value (e.g., 80 for 80%)
1841
- * @returns {string} A percentage string (e.g., "65%")
1853
+ * @param value - The text size string to parse
1854
+ * @returns {fontSizeKeys | null} The extracted size key or null if invalid
1842
1855
  */
1843
- const getResponsiveRandomWidthPercentage = ({ min, max }) => {
1844
- const randomWidth = Math.floor(Math.random() * (max - min + 1)) + min;
1845
- return `${randomWidth}%`;
1856
+ const extractSizeKey = (value) => {
1857
+ if (!value.startsWith("text-")) {
1858
+ return null;
1859
+ }
1860
+ const sizeKey = value.replace("text-", "");
1861
+ return VALID_SIZE_KEYS.find(key => key === sizeKey) ?? null;
1862
+ };
1863
+ /**
1864
+ * Calculate the height value based on the height prop and variant.
1865
+ *
1866
+ * @param height - The height value (number, CSS length, or text size key)
1867
+ * @param variant - The skeleton variant ("text" or "block")
1868
+ * @returns {string} The calculated CSS height value
1869
+ */
1870
+ const getHeightValue = (height, variant) => {
1871
+ if (typeof height === "number") {
1872
+ return `${height}px`;
1873
+ }
1874
+ const sizeKey = extractSizeKey(height);
1875
+ if (sizeKey) {
1876
+ // Text variant: use font-size × 0.7 to approximate cap-height
1877
+ // Block variant: use full line-height (for when text size keys are passed to block variant)
1878
+ return `calc(var(--font-size-${sizeKey}) * 0.7)` ;
1879
+ }
1880
+ return height;
1881
+ };
1882
+ /**
1883
+ * Calculate the vertical margin value for text variant to align with text baseline.
1884
+ * Formula: (line-height - cap-height) / 2, where cap-height = font-size × 0.7
1885
+ *
1886
+ * @param height - The height value (number, CSS length, or text size key)
1887
+ * @returns {string} The calculated CSS margin value
1888
+ */
1889
+ const getMarginValue = (height) => {
1890
+ if (typeof height === "string") {
1891
+ const sizeKey = extractSizeKey(height);
1892
+ if (sizeKey) {
1893
+ // margin = (line-height - cap-height) / 2
1894
+ // cap-height = font-size × 0.7
1895
+ // For large text sizes, this may be negative, which matches how actual text extends beyond its line-height box
1896
+ return `calc((var(--line-height-${sizeKey}) - var(--font-size-${sizeKey}) * 0.7) / 2)`;
1897
+ }
1898
+ }
1899
+ return "0";
1846
1900
  };
1847
1901
 
1848
- const cvaSkeletonContainer = cvaMerge(["flex", "flex-col"]);
1849
- const cvaSkeletonLine = cvaMerge([
1902
+ const cvaSkeleton = cvaMerge([
1850
1903
  "relative",
1851
1904
  "overflow-hidden",
1852
1905
  "rounded-lg",
@@ -1873,32 +1926,28 @@ const cvaSkeletonLine = cvaMerge([
1873
1926
  ]);
1874
1927
 
1875
1928
  /**
1876
- * Display placeholder lines before the data gets loaded to reduce load-time frustration.
1877
- * All width values are automatically constrained using CSS min() function to prevent overflow.
1929
+ * Display a single placeholder line for text content before data gets loaded to reduce load-time frustration.
1930
+ *
1931
+ * Reduces height and adds vertical margins to match the visual space text occupies within its line-height.
1932
+ * Uses text-size keys (text-xs, text-sm, text-base, etc.) for height to match actual text elements.
1933
+ *
1934
+ * For multiple text lines, use SkeletonLines component instead.
1935
+ * For shape-based elements (images, badges, buttons), use SkeletonBlock component instead.
1878
1936
  */
1879
- const SkeletonLines = memo(({ lines = 1, height = "0.75rem", width = "100%", gap = 10, maxWidth = "100%", className, "data-testid": dataTestId, }) => {
1880
- const gapStyle = typeof gap === "number" ? `${gap}px` : gap;
1881
- return (jsx("div", { "aria-label": `Loading ${lines} ${lines === 1 ? "item" : "items"}`, className: cvaSkeletonContainer({ className }), "data-testid": dataTestId, role: "status", style: { gap: gapStyle }, children: Array.from({ length: lines }, (_, index) => (jsx("div", { className: cvaSkeletonLine(), "data-testid": dataTestId ? `${dataTestId}-${index}` : `skeleton-lines-${index}`, "data-type": "loading-skeleton-line", style: {
1882
- width: getDimension({ dimension: width, index, direction: "width", maxWidth }),
1883
- height: getDimension({ dimension: height, index, direction: "height" }),
1884
- } }, index))) }));
1937
+ const SkeletonLabel = memo((props) => {
1938
+ const { width = "100%", textSize = "text-base", flexibleWidth = true, className, "data-testid": dataTestId, children, } = props;
1939
+ const widthValue = typeof width === "number" ? `${width}px` : width;
1940
+ const heightValue = getHeightValue(textSize);
1941
+ const marginValue = getMarginValue(textSize);
1942
+ return (jsx("div", { "aria-label": "Loading", className: cvaSkeleton({ className }), "data-testid": dataTestId, role: "status", style: {
1943
+ width: flexibleWidth ? "100%" : widthValue,
1944
+ maxWidth: flexibleWidth ? widthValue : undefined,
1945
+ height: heightValue,
1946
+ marginTop: marginValue,
1947
+ marginBottom: marginValue,
1948
+ }, children: children }));
1885
1949
  });
1886
- const getDimension = (params) => {
1887
- const { dimension, index } = params;
1888
- let value;
1889
- if (Array.isArray(dimension)) {
1890
- const dimValue = dimension[index] ?? dimension[0] ?? "100%";
1891
- value = typeof dimValue === "number" ? `${dimValue}px` : dimValue;
1892
- }
1893
- else {
1894
- value = typeof dimension === "number" ? `${dimension}px` : dimension;
1895
- }
1896
- // For width values, wrap in min() to ensure max container width
1897
- if (params.direction === "width") {
1898
- return `min(${value}, ${params.maxWidth})`;
1899
- }
1900
- return value;
1901
- };
1950
+ SkeletonLabel.displayName = "SkeletonLabel";
1902
1951
 
1903
1952
  const cvaContainerStyles = cvaMerge([
1904
1953
  "flex",
@@ -1991,7 +2040,7 @@ const EmptyState = ({ description, altText, image = "SEARCH_DOCUMENT", customIma
1991
2040
  return SearchDocumentSVG;
1992
2041
  }
1993
2042
  }, [image]);
1994
- return (jsx("div", { className: cvaContainerStyles({ className }), "data-testid": dataTestId ?? "empty-state", children: loading ? (jsxs(Fragment$1, { children: [jsx(Spinner, { centering: "centered", "data-testid": "spinner" }), jsx(SkeletonLines, { "data-testid": "skeleton-lines", width: 50 })] })) : (jsxs(Fragment$1, { children: [customImageSrc !== null && customImageSrc !== undefined ? (typeof customImageSrc === "string" ? (jsx("img", { alt: altText, className: cvaImgStyles(), height: 200, src: customImageSrc, width: 200 })) : (customImageSrc)) : (typeof ImageSource !== "undefined" && (jsx(ImageSource, { "data-testid": "empty-state-image", height: 200, width: 200 }, image))), description !== undefined && description !== "" ? (jsx(Text, { align: "center", size: "large", children: description })) : null, jsxs("div", { className: "mt-4 grid gap-3", children: [jsxs("div", { className: "flex gap-3", children: [secondaryAction ? (jsx(Button, { "data-testid": "empty-state-secondary-button", disabled: secondaryAction.disabled, onClick: secondaryAction.onClick, variant: "secondary", children: secondaryAction.to ? (jsx(Link, { params: secondaryAction.to.parameters, to: secondaryAction.to.pathname, children: secondaryAction.title })) : (secondaryAction.title) })) : null, primaryAction ? (jsx(Button, { "data-testid": "empty-state-primary-button", disabled: primaryAction.disabled, onClick: primaryAction.onClick, children: primaryAction.to ? (jsx(Link, { params: primaryAction.to.parameters, to: primaryAction.to.pathname, children: primaryAction.title })) : (primaryAction.title) })) : null] }), additionalHelpAction?.to ? (jsx(Button, { asChild: true, "data-testid": "empty-state-additional-button", disabled: additionalHelpAction.disabled, onClick: additionalHelpAction.onClick, suffix: jsx(Icon, { name: "ArrowTopRightOnSquare", size: "small" }), variant: "ghost", children: jsx(Link, { params: additionalHelpAction.to.parameters, target: additionalHelpAction.to.target, to: additionalHelpAction.to.pathname, children: additionalHelpAction.title }) })) : null] })] })) }));
2043
+ return (jsx("div", { className: cvaContainerStyles({ className }), "data-testid": dataTestId ?? "empty-state", children: loading ? (jsxs("div", { className: "flex w-full flex-col items-center gap-4", children: [jsx(Spinner, { centering: "centered", "data-testid": "spinner" }), jsx(SkeletonLabel, { textSize: "text-base", width: "clamp(20%, 200px, 80%)" })] })) : (jsxs(Fragment$1, { children: [customImageSrc !== null && customImageSrc !== undefined ? (typeof customImageSrc === "string" ? (jsx("img", { alt: altText, className: cvaImgStyles(), height: 200, src: customImageSrc, width: 200 })) : (customImageSrc)) : (typeof ImageSource !== "undefined" && (jsx(ImageSource, { "data-testid": "empty-state-image", height: 200, width: 200 }, image))), description !== undefined && description !== "" ? (jsx(Text, { align: "center", size: "large", children: description })) : null, jsxs("div", { className: "mt-4 grid gap-3", children: [jsxs("div", { className: "flex gap-3", children: [secondaryAction ? (jsx(Button, { "data-testid": "empty-state-secondary-button", disabled: secondaryAction.disabled, onClick: secondaryAction.onClick, variant: "secondary", children: secondaryAction.to ? (jsx(Link, { params: secondaryAction.to.parameters, to: secondaryAction.to.pathname, children: secondaryAction.title })) : (secondaryAction.title) })) : null, primaryAction ? (jsx(Button, { "data-testid": "empty-state-primary-button", disabled: primaryAction.disabled, onClick: primaryAction.onClick, children: primaryAction.to ? (jsx(Link, { params: primaryAction.to.parameters, to: primaryAction.to.pathname, children: primaryAction.title })) : (primaryAction.title) })) : null] }), additionalHelpAction?.to ? (jsx(Button, { asChild: true, "data-testid": "empty-state-additional-button", disabled: additionalHelpAction.disabled, onClick: additionalHelpAction.onClick, suffix: jsx(Icon, { name: "ArrowTopRightOnSquare", size: "small" }), variant: "ghost", children: jsx(Link, { params: additionalHelpAction.to.parameters, target: additionalHelpAction.to.target, to: additionalHelpAction.to.pathname, children: additionalHelpAction.title }) })) : null] })] })) }));
1995
2044
  };
1996
2045
 
1997
2046
  const cvaEmptyValue = cvaMerge(["text-neutral-400"]);
@@ -3930,9 +3979,189 @@ const cvaKPITrendPercentage = cvaMerge([""], {
3930
3979
  * @param {KPIProps} props - The props for the KPI component
3931
3980
  * @returns {ReactElement} KPI component
3932
3981
  */
3933
- const KPI = ({ title, value, loading = false, unit, className, "data-testid": dataTestId, tooltipLabel, variant = "default", style, ...rest }) => {
3982
+ const KPI = ({ title, value, unit, className, "data-testid": dataTestId, tooltipLabel, variant = "default", style, ...rest }) => {
3934
3983
  const isSmallVariant = variant === "small";
3935
- return (jsx(Tooltip, { className: "min-w-8 shrink-0", "data-testid": dataTestId ? `${dataTestId}-tooltip` : undefined, disabled: tooltipLabel === undefined || tooltipLabel === "", label: tooltipLabel, placement: "bottom", children: jsxs("div", { className: cvaKPI({ variant, className }), "data-testid": dataTestId, style: style, ...rest, children: [loading ? (jsx(SkeletonLines, { className: twMerge("flex", "items-center", "flex-row", isSmallVariant ? "h-4" : "h-5"), "data-testid": dataTestId ? `${dataTestId}-title-loading` : undefined, height: isSmallVariant ? themeFontSize.xs : themeFontSize.sm, lines: 1, width: "80%" })) : (jsx(Text, { className: twMerge("truncate", "whitespace-nowrap"), "data-testid": dataTestId ? `${dataTestId}-title` : undefined, size: isSmallVariant ? "small" : "medium", subtle: true, weight: isSmallVariant ? "normal" : "bold", children: title })), jsx("div", { className: twMerge("truncate", "whitespace-nowrap"), children: loading ? (jsx(SkeletonLines, { className: "flex h-7 flex-row items-center", "data-testid": dataTestId ? `${dataTestId}-value-loading` : undefined, height: themeFontSize.base, lines: 1, width: "100%" })) : (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] })) })] }) }));
3984
+ return (jsx(Tooltip, { className: "min-w-8 shrink-0", "data-testid": dataTestId ? `${dataTestId}-tooltip` : undefined, disabled: tooltipLabel === undefined || tooltipLabel === "", label: tooltipLabel, placement: "bottom", children: jsxs("div", { className: cvaKPI({ variant, className }), "data-testid": dataTestId, style: style, ...rest, children: [jsx(Text, { className: twMerge("truncate", "whitespace-nowrap"), "data-testid": dataTestId ? `${dataTestId}-title` : undefined, size: isSmallVariant ? "small" : "medium", subtle: true, weight: isSmallVariant ? "normal" : "bold", children: title }), jsx("div", { className: twMerge("truncate", "whitespace-nowrap"), children: 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] }) })] }) }));
3985
+ };
3986
+
3987
+ /**
3988
+ * Display a single placeholder block for shape-based elements before data gets loaded to reduce load-time frustration.
3989
+ *
3990
+ * Fills the full height for images, badges, buttons, avatars, and other shape-based elements.
3991
+ * Uses numbers or CSS length values for height.
3992
+ *
3993
+ * For text content, use SkeletonLabel component instead.
3994
+ * For multiple text lines, use SkeletonLines component instead.
3995
+ */
3996
+ const SkeletonBlock = memo((props) => {
3997
+ const { width = "100%", height = 16, flexibleWidth = false, className, "data-testid": dataTestId, children } = props;
3998
+ const widthValue = typeof width === "number" ? `${width}px` : width;
3999
+ const heightValue = typeof height === "number" ? `${height}px` : height;
4000
+ return (jsx("div", { "aria-label": "Loading", className: cvaSkeleton({ className }), "data-testid": dataTestId, role: "status", style: {
4001
+ width: flexibleWidth ? "100%" : widthValue,
4002
+ maxWidth: flexibleWidth ? widthValue : undefined,
4003
+ height: heightValue,
4004
+ }, children: children }));
4005
+ });
4006
+ SkeletonBlock.displayName = "SkeletonBlock";
4007
+
4008
+ /**
4009
+ * Generates a random width percentage string for skeleton loading components.
4010
+ *
4011
+ * @param {object} params - The parameter object
4012
+ * @param {number} params.min - Minimum percentage value (e.g., 30 for 30%)
4013
+ * @param {number} params.max - Maximum percentage value (e.g., 80 for 80%)
4014
+ * @returns {string} A percentage string (e.g., "65%")
4015
+ */
4016
+ const getResponsiveRandomWidthPercentage = ({ min, max }) => {
4017
+ const randomWidth = Math.floor(Math.random() * (max - min + 1)) + min;
4018
+ return `${randomWidth}%`;
4019
+ };
4020
+
4021
+ /**
4022
+ * Preset configurations for common skeleton patterns.
4023
+ */
4024
+ const PRESET_CONFIGURATIONS = {
4025
+ "short-paragraph": [
4026
+ { textSize: "text-base", width: "100%" },
4027
+ { textSize: "text-base", width: "100%" },
4028
+ { textSize: "text-base", width: "60%" },
4029
+ ],
4030
+ paragraph: [
4031
+ { textSize: "text-base", width: "100%" },
4032
+ { textSize: "text-base", width: "100%" },
4033
+ { textSize: "text-base", width: "95%" },
4034
+ { textSize: "text-base", width: "90%" },
4035
+ { textSize: "text-base", width: "65%" },
4036
+ ],
4037
+ "long-paragraph": [
4038
+ { textSize: "text-base", width: "100%" },
4039
+ { textSize: "text-base", width: "100%" },
4040
+ { textSize: "text-base", width: "98%" },
4041
+ { textSize: "text-base", width: "95%" },
4042
+ { textSize: "text-base", width: "92%" },
4043
+ { textSize: "text-base", width: "88%" },
4044
+ { textSize: "text-base", width: "85%" },
4045
+ { textSize: "text-base", width: "60%" },
4046
+ ],
4047
+ "title-paragraph": [
4048
+ { textSize: "text-2xl", width: "65%" },
4049
+ { textSize: "text-base", width: "100%" },
4050
+ { textSize: "text-base", width: "95%" },
4051
+ { textSize: "text-base", width: "70%" },
4052
+ ],
4053
+ "heading-text": [
4054
+ { textSize: "text-xl", width: "60%" },
4055
+ { textSize: "text-base", width: "100%" },
4056
+ { textSize: "text-base", width: "100%" },
4057
+ { textSize: "text-base", width: "90%" },
4058
+ { textSize: "text-base", width: "65%" },
4059
+ ],
4060
+ article: [
4061
+ { textSize: "text-3xl", width: "70%" },
4062
+ { textSize: "text-sm", width: "40%" },
4063
+ { textSize: "text-base", width: "100%" },
4064
+ { textSize: "text-base", width: "100%" },
4065
+ { textSize: "text-base", width: "95%" },
4066
+ { textSize: "text-base", width: "90%" },
4067
+ { textSize: "text-base", width: "65%" },
4068
+ ],
4069
+ };
4070
+
4071
+ const cvaSkeletonContainer = cvaMerge(["flex", "flex-col"]);
4072
+ cvaMerge([
4073
+ "relative",
4074
+ "overflow-hidden",
4075
+ "rounded-lg",
4076
+ // Gradient background
4077
+ "bg-gradient-to-r",
4078
+ "from-gray-200/80",
4079
+ "via-gray-300/60",
4080
+ "to-gray-200/80",
4081
+ // Pulse animation
4082
+ "animate-pulse",
4083
+ // Shimmer overlay
4084
+ "before:absolute",
4085
+ "before:inset-0",
4086
+ "before:bg-gradient-to-r",
4087
+ "before:from-transparent",
4088
+ "before:via-white/50",
4089
+ "before:to-transparent",
4090
+ "before:opacity-0",
4091
+ "before:animate-pulse",
4092
+ // Smooth transitions for accessibility
4093
+ "transition-all",
4094
+ "duration-300",
4095
+ "ease-in-out",
4096
+ ]);
4097
+
4098
+ /**
4099
+ * Display multiple placeholder lines before data gets loaded to reduce load-time frustration.
4100
+ *
4101
+ * Supports three modes:
4102
+ * - **Uniform mode** (default): Display identical lines using `count` prop
4103
+ * - **Custom mode**: Display customized lines with per-line configuration using `variant="custom"` and `lines` prop
4104
+ * - **Preset mode**: Display common patterns using `variant="preset"` and `preset` name
4105
+ *
4106
+ * Built on top of the [SkeletonLabel](?path=/docs/components-loading-states-skeletonlabel--docs) component for text-specific margins and sizing.
4107
+ *
4108
+ * @example
4109
+ * // Uniform lines (simple mode)
4110
+ * <SkeletonLines count={3} textSize="text-base" variant="uniform" />
4111
+ * @example
4112
+ * // Custom lines (advanced mode)
4113
+ * <SkeletonLines
4114
+ * variant="custom"
4115
+ * lines={[
4116
+ * { textSize: "text-lg", width: "100%" },
4117
+ * { textSize: "text-base", width: "95%" },
4118
+ * { textSize: "text-base", width: "70%" }
4119
+ * ]}
4120
+ * />
4121
+ * @example
4122
+ * // Preset lines (quick patterns)
4123
+ * <SkeletonLines variant="preset" preset="title-paragraph" />
4124
+ */
4125
+ const SkeletonLines = memo((props) => {
4126
+ const { className, "data-testid": dataTestId } = props;
4127
+ // Generate line configs based on variant
4128
+ let lineConfigs;
4129
+ if (props.variant === "preset") {
4130
+ // TypeScript now knows props is PresetSkeletonLinesProps
4131
+ lineConfigs = PRESET_CONFIGURATIONS[props.preset];
4132
+ }
4133
+ else if (props.variant === "custom") {
4134
+ // TypeScript now knows props is CustomSkeletonLinesProps
4135
+ lineConfigs = props.lines;
4136
+ }
4137
+ else {
4138
+ // TypeScript now knows props is UniformSkeletonLinesProps
4139
+ lineConfigs = Array.from({ length: props.count }, () => ({
4140
+ textSize: props.textSize,
4141
+ width: props.width,
4142
+ flexibleWidth: props.flexibleWidth,
4143
+ className: props.lineClassName,
4144
+ }));
4145
+ }
4146
+ const lineCount = lineConfigs.length;
4147
+ return (jsx("div", { "aria-label": `Loading ${lineCount} ${lineCount === 1 ? "item" : "items"}`, className: cvaSkeletonContainer({ className }), "data-testid": dataTestId, "data-variant": props.variant, role: "status", ...(props.variant === "preset" && { "data-preset": props.preset }), children: lineConfigs.map((config, index) => (jsx(SkeletonLabel, { className: config.className, "data-testid": dataTestId ? `${dataTestId}-${index}` : undefined, flexibleWidth: config.flexibleWidth ?? true, textSize: config.textSize ?? "text-base", width: config.width ?? "100%" }, index))) }));
4148
+ });
4149
+ SkeletonLines.displayName = "SkeletonLines";
4150
+
4151
+ /**
4152
+ * Skeleton loading indicator that mimics the KPI component structure.
4153
+ * Uses the same layout, spacing, and visual hierarchy as KPI.
4154
+ */
4155
+ const KPISkeleton = ({ variant = "default", className, "data-testid": dataTestId, style, ...rest }) => {
4156
+ const isSmallVariant = variant === "small";
4157
+ // Generate stable random widths once and never change them
4158
+ const lineWidths = useMemo(() => {
4159
+ return {
4160
+ title: getResponsiveRandomWidthPercentage({ min: 60, max: 85 }),
4161
+ value: getResponsiveRandomWidthPercentage({ min: 70, max: 100 }),
4162
+ };
4163
+ }, []);
4164
+ return (jsxs("div", { className: cvaKPI({ variant, className }), "data-testid": dataTestId, style: style, ...rest, children: [jsx("div", { className: twMerge("flex", "items-center", "flex-row", isSmallVariant ? "h-4" : "h-5"), children: jsx(SkeletonLabel, { "data-testid": dataTestId ? `${dataTestId}-title-loading` : undefined, textSize: isSmallVariant ? "text-xs" : "text-sm", width: lineWidths.title }) }), jsx("div", { className: twMerge("truncate", "whitespace-nowrap", "flex h-7 flex-row items-center"), children: jsx(SkeletonLabel, { "data-testid": dataTestId ? `${dataTestId}-value-loading` : undefined, textSize: isSmallVariant ? "text-xs" : "text-lg", width: lineWidths.value }) })] }));
3936
4165
  };
3937
4166
 
3938
4167
  /**
@@ -4088,6 +4317,8 @@ const cvaKPICard = cvaMerge([
4088
4317
  isActive: false,
4089
4318
  },
4090
4319
  });
4320
+ const cvaKPICardBody = cvaMerge(["grid", "gap-2", "px-3", "pb-2", "pt-3"]);
4321
+ const cvaKPICardHeader = cvaMerge(["grid", "grid-cols-[1fr_auto]", "justify-between", "gap-2"]);
4091
4322
  const cvaKPIIconContainer = cvaMerge(["flex", "items-center", "justify-center", "w-7", "h-7", "rounded-md", "text-white"], {
4092
4323
  variants: {
4093
4324
  iconColor: {
@@ -4106,15 +4337,23 @@ const cvaKPIIconContainer = cvaMerge(["flex", "items-center", "justify-center",
4106
4337
  * @param {KPICardProps} props - The props for the KPICard component
4107
4338
  * @returns {ReactElement} KPICard component
4108
4339
  */
4109
- const KPICard = ({ isActive = false, onClick, className, "data-testid": dataTestId, children, iconName = undefined, iconColor = "info", loading = false, notice, valueBar, trends, unit, ...rest }) => {
4110
- const isClickable = Boolean(onClick !== undefined && loading !== true);
4340
+ const KPICard = ({ isActive = false, onClick, className, "data-testid": dataTestId, children, iconName = undefined, iconColor = "info", notice, valueBar, trends, unit, ...rest }) => {
4341
+ const isClickable = Boolean(onClick !== undefined);
4111
4342
  return (jsx(Card, { className: cvaKPICard({
4112
4343
  isClickable,
4113
4344
  isActive,
4114
4345
  className,
4115
- }), "data-testid": dataTestId ? dataTestId : undefined, onClick: onClick, children: jsxs(CardBody, { className: "grid gap-2 px-3 pb-2 pt-3", gap: "none", padding: "none", children: [jsxs("div", { className: "grid grid-cols-[1fr_auto] justify-between gap-2", children: [jsx(KPI, { ...rest, className: "p-0", "data-testid": dataTestId ? `${dataTestId}-kpi` : undefined, loading: loading, unit: unit }), iconName ? (jsx("div", { className: cvaKPIIconContainer({ iconColor }), children: jsx(Icon, { name: iconName, size: "small", type: "solid" }) })) : null] }), trends !== undefined && trends.length > 0 ? (loading ? (jsx(SkeletonLines, { className: "h-4", "data-testid": dataTestId ? `${dataTestId}-trend-indicator-loading` : undefined, height: themeFontSize.xs, lines: 1, width: "100%" })) : (jsx(TrendIndicators, { "data-testid": dataTestId ? `${dataTestId}-trend-indicators` : undefined, trends: trends }))) : null, valueBar !== undefined ? (loading ? (jsx(SkeletonLines, { className: "h-4", "data-testid": dataTestId ? `${dataTestId}-value-bar-loading` : undefined, gap: 0, height: themeFontSize.xs, lines: 1, width: "100%" })) : (jsx(ValueBar, { className: "h-2", "data-testid": dataTestId ? `${dataTestId}-value-bar` : undefined, ...valueBar }))) : null, notice !== undefined ? (loading ? (jsx(SkeletonLines, { className: "h-4", "data-testid": dataTestId ? `${dataTestId}-notice-loading` : undefined, gap: 0, height: themeFontSize.xs, lines: 1, width: "100%" })) : (
4346
+ }), "data-testid": dataTestId ? dataTestId : undefined, onClick: onClick, children: jsxs(CardBody, { className: cvaKPICardBody(), gap: "none", padding: "none", children: [jsxs("div", { className: cvaKPICardHeader(), children: [jsx(KPI, { ...rest, className: "p-0", "data-testid": dataTestId ? `${dataTestId}-kpi` : undefined, unit: unit }), iconName ? (jsx("div", { className: cvaKPIIconContainer({ iconColor }), children: jsx(Icon, { name: iconName, size: "small", type: "solid" }) })) : null] }), trends !== undefined && trends.length > 0 ? (jsx(TrendIndicators, { "data-testid": dataTestId ? `${dataTestId}-trend-indicators` : undefined, trends: trends })) : null, valueBar !== undefined ? (jsx(ValueBar, { className: "h-2", "data-testid": dataTestId ? `${dataTestId}-value-bar` : undefined, ...valueBar })) : null, notice !== undefined ? (
4116
4347
  // NOTE: Can't use Notice component here due to the non-flexible text styling options
4117
- jsxs("div", { className: "flex items-center gap-1 truncate", "data-testid": dataTestId ? `${dataTestId}-notice` : undefined, children: [notice.iconName ? (jsx(Icon, { color: notice.iconColor, "data-testid": dataTestId ? `${dataTestId}-notice-icon` : undefined, name: notice.iconName, size: "small" })) : null, jsx(Text, { className: "truncate text-neutral-900", "data-testid": dataTestId ? `${dataTestId}-notice-label` : undefined, size: "small", children: notice.label })] }))) : null, children] }) }));
4348
+ jsxs("div", { className: "flex items-center gap-1 truncate", "data-testid": dataTestId ? `${dataTestId}-notice` : undefined, children: [notice.iconName ? (jsx(Icon, { color: notice.iconColor, "data-testid": dataTestId ? `${dataTestId}-notice-icon` : undefined, name: notice.iconName, size: "small" })) : null, jsx(Text, { className: "truncate text-neutral-900", "data-testid": dataTestId ? `${dataTestId}-notice-label` : undefined, size: "small", children: notice.label })] })) : null, children] }) }));
4349
+ };
4350
+
4351
+ /**
4352
+ * Skeleton loading indicator that mimics the KPICard component structure.
4353
+ * Uses the same layout, spacing, and visual hierarchy as KPICard.
4354
+ */
4355
+ const KPICardSkeleton = ({ hasIcon = false, hasTrends = false, hasValueBar = false, hasNotice = false, children, className, "data-testid": dataTestId, style, ...rest }) => {
4356
+ return (jsx(Card, { className: cvaKPICard({ className }), "data-testid": dataTestId, style: style, ...rest, children: jsxs(CardBody, { className: cvaKPICardBody(), gap: "none", padding: "none", children: [jsxs("div", { className: cvaKPICardHeader(), children: [jsx(KPISkeleton, { className: "p-0", "data-testid": dataTestId ? `${dataTestId}-kpi` : undefined }), hasIcon ? (jsx(SkeletonBlock, { "data-testid": dataTestId ? `${dataTestId}-icon-loading` : undefined, height: 28, width: 28 })) : null] }), hasTrends ? (jsx(SkeletonLabel, { "data-testid": dataTestId ? `${dataTestId}-trend-indicator-loading` : undefined, textSize: "text-xs", width: "100%" })) : null, hasValueBar ? (jsx(SkeletonLabel, { "data-testid": dataTestId ? `${dataTestId}-value-bar-loading` : undefined, textSize: "text-xs", width: "100%" })) : null, hasNotice ? (jsx(SkeletonLabel, { "data-testid": dataTestId ? `${dataTestId}-notice-loading` : undefined, textSize: "text-xs", width: "100%" })) : null, children] }) }));
4118
4357
  };
4119
4358
 
4120
4359
  const cvaListContainer = cvaMerge(["overflow-y-auto", "overflow-x-hidden", "h-full"], {
@@ -4157,124 +4396,6 @@ const cvaListItem$1 = cvaMerge(["absolute", "top-0", "left-0", "w-full"], {
4157
4396
  },
4158
4397
  });
4159
4398
 
4160
- const cvaSkeleton = cvaMerge([
4161
- "relative",
4162
- "overflow-hidden",
4163
- "rounded-lg",
4164
- // Gradient background
4165
- "bg-gradient-to-r",
4166
- "from-gray-200/80",
4167
- "via-gray-300/60",
4168
- "to-gray-200/80",
4169
- // Pulse animation
4170
- "animate-pulse",
4171
- // Shimmer overlay
4172
- "before:absolute",
4173
- "before:inset-0",
4174
- "before:bg-gradient-to-r",
4175
- "before:from-transparent",
4176
- "before:via-white/50",
4177
- "before:to-transparent",
4178
- "before:opacity-0",
4179
- "before:animate-pulse",
4180
- // Smooth transitions for accessibility
4181
- "transition-all",
4182
- "duration-300",
4183
- "ease-in-out",
4184
- ]);
4185
-
4186
- const VALID_SIZE_KEYS = [
4187
- "xs",
4188
- "sm",
4189
- "base",
4190
- "lg",
4191
- "xl",
4192
- "2xl",
4193
- "3xl",
4194
- "4xl",
4195
- "5xl",
4196
- "6xl",
4197
- "7xl",
4198
- "8xl",
4199
- "9xl",
4200
- ];
4201
- /**
4202
- * Extract the size key from a text size string (e.g., "text-base" → "base").
4203
- *
4204
- * @param value - The text size string to parse
4205
- * @returns {fontSizeKeys | null} The extracted size key or null if invalid
4206
- */
4207
- const extractSizeKey = (value) => {
4208
- if (!value.startsWith("text-")) {
4209
- return null;
4210
- }
4211
- const sizeKey = value.replace("text-", "");
4212
- return VALID_SIZE_KEYS.find(key => key === sizeKey) ?? null;
4213
- };
4214
- /**
4215
- * Calculate the height value based on the height prop and variant.
4216
- *
4217
- * @param height - The height value (number, CSS length, or text size key)
4218
- * @param variant - The skeleton variant ("text" or "block")
4219
- * @returns {string} The calculated CSS height value
4220
- */
4221
- const getHeightValue = (height, variant) => {
4222
- if (typeof height === "number") {
4223
- return `${height}px`;
4224
- }
4225
- const sizeKey = extractSizeKey(height);
4226
- if (sizeKey) {
4227
- // Text variant: use font-size × 0.7 to approximate cap-height
4228
- // Block variant: use full line-height (for when text size keys are passed to block variant)
4229
- return variant === "text" ? `calc(var(--font-size-${sizeKey}) * 0.7)` : `var(--line-height-${sizeKey})`;
4230
- }
4231
- return height;
4232
- };
4233
- /**
4234
- * Calculate the vertical margin value for text variant to align with text baseline.
4235
- * Formula: (line-height - cap-height) / 2, where cap-height = font-size × 0.7
4236
- *
4237
- * @param height - The height value (number, CSS length, or text size key)
4238
- * @returns {string} The calculated CSS margin value
4239
- */
4240
- const getMarginValue = (height) => {
4241
- if (typeof height === "string") {
4242
- const sizeKey = extractSizeKey(height);
4243
- if (sizeKey) {
4244
- // margin = (line-height - cap-height) / 2
4245
- // cap-height = font-size × 0.7
4246
- // For large text sizes, this may be negative, which matches how actual text extends beyond its line-height box
4247
- return `calc((var(--line-height-${sizeKey}) - var(--font-size-${sizeKey}) * 0.7) / 2)`;
4248
- }
4249
- }
4250
- return "0";
4251
- };
4252
- /**
4253
- * Display a single placeholder line before data gets loaded to reduce load-time frustration.
4254
- *
4255
- * Use `variant="text"` with text-size keys for text placeholders.
4256
- * Use `variant="block"` with numbers/CSS for images, badges, buttons, and other shape-based elements.
4257
- * Pass children to create custom skeleton layouts.
4258
- */
4259
- const Skeleton = memo((props) => {
4260
- const { width = "100%", className, "data-testid": dataTestId, children } = props;
4261
- const variant = props.variant ?? "text";
4262
- const height = props.height ?? (variant === "text" ? "text-base" : 16);
4263
- const flexibleWidth = props.flexibleWidth ?? variant === "text";
4264
- const widthValue = typeof width === "number" ? `${width}px` : width;
4265
- const isTextVariant = variant === "text";
4266
- const heightValue = getHeightValue(height, variant);
4267
- const marginValue = isTextVariant ? getMarginValue(height) : undefined;
4268
- return (jsx("div", { "aria-label": "Loading", className: cvaSkeleton({ className }), "data-testid": dataTestId, role: "status", style: {
4269
- width: flexibleWidth ? "100%" : widthValue,
4270
- maxWidth: flexibleWidth ? widthValue : undefined,
4271
- height: heightValue,
4272
- marginTop: marginValue,
4273
- marginBottom: marginValue,
4274
- }, children: children }));
4275
- });
4276
- Skeleton.displayName = "Skeleton";
4277
-
4278
4399
  const cvaListItem = cvaMerge(["py-3", "px-4", "min-h-14", "w-full", "flex", "justify-between", "items-center"]);
4279
4400
  const cvaMainInformationClass = cvaMerge(["grid", "items-center", "text-sm", "gap-2"], {
4280
4401
  variants: {
@@ -4323,7 +4444,7 @@ const ListItemSkeleton = ({ hasThumbnail = DEFAULT_SKELETON_LIST_ITEM_PROPS.hasT
4323
4444
  details: getResponsiveRandomWidthPercentage({ min: 25, max: 45 }),
4324
4445
  };
4325
4446
  }, []);
4326
- return (jsxs("div", { className: cvaListItem({ className: "w-full" }), children: [jsxs("div", { className: cvaMainInformationClass({ hasThumbnail, className: "w-full" }), children: [hasThumbnail ? (jsx("div", { className: cvaThumbnailContainer({ className: "bg-gray-200" }), children: jsx("div", { className: twMerge("bg-gray-300", thumbnailShape === "circle" ? "rounded-full" : "rounded"), style: { width: 20, height: 20 } }) })) : null, jsxs("div", { className: cvaTextContainer(), children: [jsx("div", { className: cvaTitleRow(), children: jsx(Skeleton, { height: "text-sm", width: lineWidths.title }) }), hasDescription ? (jsx("div", { className: cvaDescriptionRow(), children: jsx(Skeleton, { height: "text-xs", width: lineWidths.description }) })) : null, hasMeta ? (jsx("div", { className: cvaMetaRow(), children: jsx(Skeleton, { height: "text-xs", width: lineWidths.meta }) })) : null] })] }), hasDetails ? (jsx("div", { className: cvaDetailsContainer(), children: jsx(Skeleton, { height: "text-sm", width: lineWidths.details }) })) : null] }));
4447
+ return (jsxs("div", { className: cvaListItem({ className: "w-full" }), children: [jsxs("div", { className: cvaMainInformationClass({ hasThumbnail, className: "w-full" }), children: [hasThumbnail ? (jsx("div", { className: cvaThumbnailContainer({ className: "bg-gray-200" }), children: jsx("div", { className: twMerge("bg-gray-300", thumbnailShape === "circle" ? "rounded-full" : "rounded"), style: { width: 20, height: 20 } }) })) : null, jsxs("div", { className: cvaTextContainer(), children: [jsx("div", { className: cvaTitleRow(), children: jsx(SkeletonLabel, { textSize: "text-sm", width: lineWidths.title }) }), hasDescription ? (jsx("div", { className: cvaDescriptionRow(), children: jsx(SkeletonLabel, { textSize: "text-xs", width: lineWidths.description }) })) : null, hasMeta ? (jsx("div", { className: cvaMetaRow(), children: jsx(SkeletonLabel, { textSize: "text-xs", width: lineWidths.meta }) })) : null] })] }), hasDetails ? (jsx("div", { className: cvaDetailsContainer(), children: jsx(SkeletonLabel, { textSize: "text-sm", width: lineWidths.details }) })) : null] }));
4327
4448
  };
4328
4449
 
4329
4450
  /**
@@ -5230,9 +5351,12 @@ const PageContent = ({ className, children, "data-testid": dataTestId, layout, }
5230
5351
  return (jsx("div", { className: cvaPageContent({ className, layout }), "data-testid": dataTestId ? dataTestId : "page-content", children: children }));
5231
5352
  };
5232
5353
 
5233
- const LoadingContent = () => (jsx("div", { className: "flex flex-row items-center gap-3", "data-testid": "kpi-card-loading-content", children: jsx("div", { className: "w-full", children: jsx(SkeletonLines, { gap: 3, height: [16, 25], lines: 2, width: [50, 40] }) }) }));
5354
+ const LoadingContent = () => (jsx("div", { className: "flex flex-row items-center", "data-testid": "kpi-card-loading-content", children: jsx("div", { className: "w-full", children: jsx(SkeletonLines, { lines: [
5355
+ { textSize: "text-xs", width: 50 },
5356
+ { textSize: "text-lg", width: 40 },
5357
+ ], variant: "custom" }) }) }));
5234
5358
  /**
5235
- * The PageHeaderKpiMetrics component is used to render the KPI metrics in the PageHeader component.
5359
+ * Renders KPI metrics in the PageHeader component.
5236
5360
  *
5237
5361
  * @param {object} props - The props for the PageHeaderKpiMetrics component
5238
5362
  * @param {Array<PageHeaderKpiMetricsType>} props.kpiMetrics - The KPI metrics to render
@@ -5471,7 +5595,7 @@ const Pagination = ({ previousPage, nextPage, canPreviousPage = false, canNextPa
5471
5595
  onPageChange?.({ from, to, description });
5472
5596
  }, [page, onPageChange, previousPage, nextPage]);
5473
5597
  if (loading) {
5474
- return (jsx("div", { className: cvaPagination({ className }), children: jsx(SkeletonLines, { height: 16, width: 150 }) }));
5598
+ return (jsx("div", { className: cvaPagination({ className }), children: jsx(SkeletonLabel, { textSize: "text-sm", width: 150 }) }));
5475
5599
  }
5476
5600
  return (jsxs("div", { className: cvaPagination({ className }), "data-testid": dataTestId, children: [jsx(IconButton, { "data-testid": "prev-page", disabled: cursorBase ? !canPreviousPage || false : page !== undefined && page <= 0, icon: jsx(Icon, { name: "ChevronLeft", size: "small" }), onClick: () => handlePageChange("prev"), size: "small", variant: "ghost-neutral" }), !cursorBase && (jsxs(Fragment$1, { children: [jsx("div", { className: cvaPaginationText(), "data-testid": "current-page", children: currentPage }), jsx("div", { className: cvaPaginationText(), "data-testid": "page-count", children: pageCount !== null && pageCount !== undefined && getTranslatedCount ? getTranslatedCount(pageCount) : null })] })), jsx(IconButton, { "data-testid": "next-page", disabled: cursorBase
5477
5601
  ? !canNextPage || false
@@ -5710,7 +5834,7 @@ const PreferenceCardSkeleton = ({ hasIcon = DEFAULT_SKELETON_PREFERENCE_CARD_PRO
5710
5834
  description: getRandomWidth(160, 240),
5711
5835
  };
5712
5836
  }, []);
5713
- return (jsxs("div", { className: cvaPreferenceCard(), children: [hasInput ? (jsx("div", { className: cvaInputContainer({ itemPlacement: "center" }), children: jsx(Skeleton, { height: 20, variant: "block", width: 20 }) })) : null, jsx("div", { className: cvaContentWrapper(), children: jsx(GridAreas, { ...gridAreas, className: cvaContentContainer({ itemPlacement: "center" }), children: slots => (jsxs(Fragment$1, { children: [hasIcon ? (jsx("div", { ...slots.icon, children: jsx(Skeleton, { className: cvaIconBackground({ disabled: false }), height: 32, variant: "block", width: 32 }) })) : null, jsxs("div", { ...slots.information, className: "min-w-0 flex-1", children: [jsx("div", { className: "grid min-w-0 grid-cols-[1fr_auto] items-center gap-2", children: jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [jsx(Skeleton, { height: "text-sm", width: lineWidths.title }), hasTitleTag ? jsx(TagSkeleton, { size: "small" }) : null] }) }), jsx(Skeleton, { height: "text-xs", width: lineWidths.description })] }), hasCardTag ? (jsx("div", { ...slots.cardTag, className: "justify-self-end", children: jsx(TagSkeleton, { size: "medium" }) })) : null] })) }) })] }));
5837
+ return (jsxs("div", { className: cvaPreferenceCard(), children: [hasInput ? (jsx("div", { className: cvaInputContainer({ itemPlacement: "center" }), children: jsx(SkeletonBlock, { height: 20, width: 20 }) })) : null, jsx("div", { className: cvaContentWrapper(), children: jsx(GridAreas, { ...gridAreas, className: cvaContentContainer({ itemPlacement: "center" }), children: slots => (jsxs(Fragment$1, { children: [hasIcon ? (jsx("div", { ...slots.icon, children: jsx(SkeletonBlock, { className: cvaIconBackground({ disabled: false }), height: 32, width: 32 }) })) : null, jsxs("div", { ...slots.information, className: "min-w-0 flex-1", children: [jsx("div", { className: "grid min-w-0 grid-cols-[1fr_auto] items-center gap-2", children: jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [jsx(SkeletonLabel, { textSize: "text-sm", width: lineWidths.title }), hasTitleTag ? jsx(TagSkeleton, { size: "small" }) : null] }) }), jsx(SkeletonLabel, { textSize: "text-xs", width: lineWidths.description })] }), hasCardTag ? (jsx("div", { ...slots.cardTag, className: "justify-self-end", children: jsx(TagSkeleton, { size: "medium" }) })) : null] })) }) })] }));
5714
5838
  };
5715
5839
  /**
5716
5840
  * Simple tag skeleton for use within PreferenceCardSkeleton.
@@ -5719,7 +5843,7 @@ const PreferenceCardSkeleton = ({ hasIcon = DEFAULT_SKELETON_PREFERENCE_CARD_PRO
5719
5843
  const TagSkeleton = ({ size }) => {
5720
5844
  const width = useMemo(() => getRandomWidth(40, 64), []);
5721
5845
  const height = size === "small" ? 18 : 24;
5722
- return jsx(Skeleton, { className: "rounded-full", height: height, variant: "block", width: width });
5846
+ return jsx(SkeletonBlock, { className: "rounded-full", height: height, width: width });
5723
5847
  };
5724
5848
 
5725
5849
  function useConfirmExit(confirmExit, when = true) {
@@ -7165,4 +7289,4 @@ const useWindowActivity = ({ onFocus, onBlur, skip = false } = { onBlur: undefin
7165
7289
  return useMemo(() => ({ focused }), [focused]);
7166
7290
  };
7167
7291
 
7168
- 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, 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, Skeleton, 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, getResponsiveRandomWidthPercentage, getValueBarColorByValue, iconColorNames, iconPalette, noPagination, preferenceCardGrid, useClickOutside, useContainerBreakpoints, useContinuousTimeout, useCopyToClipboard, useCustomEncoding, useDebounce, useDevicePixelRatio, useElevatedReducer, useElevatedState, useGridAreas, useHover, useInfiniteScroll, useIsFirstRender, useIsFullscreen, useIsTextTruncated, useList, useListItemHeight, useLocalStorage, useLocalStorageReducer, useMeasure, useMergeRefs, useModifierKey, useOverflowItems, usePopoverContext, usePrevious, usePrompt, useRelayPagination, useResize, useScrollBlock, useScrollDetection, useSelfUpdatingRef, useTextSearch, useTimeout, useViewportBreakpoints, useWatch, useWindowActivity };
7292
+ 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, getResponsiveRandomWidthPercentage, getValueBarColorByValue, iconColorNames, iconPalette, noPagination, preferenceCardGrid, useClickOutside, useContainerBreakpoints, useContinuousTimeout, useCopyToClipboard, useCustomEncoding, useDebounce, useDevicePixelRatio, useElevatedReducer, useElevatedState, useGridAreas, useHover, useInfiniteScroll, useIsFirstRender, useIsFullscreen, useIsTextTruncated, useList, useListItemHeight, useLocalStorage, useLocalStorageReducer, useMeasure, useMergeRefs, useModifierKey, useOverflowItems, usePopoverContext, usePrevious, usePrompt, 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.14.5",
3
+ "version": "1.14.11",
4
4
  "repository": "https://github.com/Trackunit/manager",
5
5
  "license": "SEE LICENSE IN LICENSE.txt",
6
6
  "engines": {
@@ -14,10 +14,10 @@
14
14
  "@floating-ui/react": "^0.26.25",
15
15
  "string-ts": "^2.0.0",
16
16
  "tailwind-merge": "^2.0.0",
17
- "@trackunit/ui-design-tokens": "1.10.4",
18
- "@trackunit/css-class-variance-utilities": "1.10.4",
19
- "@trackunit/shared-utils": "1.12.4",
20
- "@trackunit/ui-icons": "1.10.4",
17
+ "@trackunit/ui-design-tokens": "1.10.9",
18
+ "@trackunit/css-class-variance-utilities": "1.10.9",
19
+ "@trackunit/shared-utils": "1.12.9",
20
+ "@trackunit/ui-icons": "1.10.9",
21
21
  "@tanstack/react-router": "1.114.29",
22
22
  "es-toolkit": "^1.39.10",
23
23
  "@tanstack/react-virtual": "3.13.12",
@@ -14,10 +14,6 @@ export interface KPIProps extends CommonProps, Styleable {
14
14
  * The unit of the KPI
15
15
  */
16
16
  unit: string;
17
- /**
18
- * Whether or not to show the loading state
19
- */
20
- loading?: boolean;
21
17
  /**
22
18
  * Label for the tooltip
23
19
  */
@@ -33,4 +29,4 @@ export interface KPIProps extends CommonProps, Styleable {
33
29
  * @param {KPIProps} props - The props for the KPI component
34
30
  * @returns {ReactElement} KPI component
35
31
  */
36
- export declare const KPI: ({ title, value, loading, unit, className, "data-testid": dataTestId, tooltipLabel, variant, style, ...rest }: KPIProps) => ReactElement;
32
+ export declare const KPI: ({ title, value, unit, className, "data-testid": dataTestId, tooltipLabel, variant, style, ...rest }: KPIProps) => ReactElement;