@trackunit/react-components 1.14.6 → 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.cjs.js CHANGED
@@ -1834,21 +1834,74 @@ const DetailsList = ({ details, className, hasLink = false }) => {
1834
1834
  return (jsxRuntime.jsx("div", { className: cvaDetailsList({ className, hasLink }), children: details.map((value, index, array) => (jsxRuntime.jsxs(react.Fragment, { children: [jsxRuntime.jsx("span", { className: cvaDetailsListItem({ className }), children: value }), index < array.length - 1 && (jsxRuntime.jsx("div", { className: "mx-0.5 flex items-center", children: jsxRuntime.jsx(Icon, { className: "w-4 text-neutral-300", color: "neutral", name: "Slash", size: "small" }) }))] }, index))) }));
1835
1835
  };
1836
1836
 
1837
+ const VALID_SIZE_KEYS = [
1838
+ "xs",
1839
+ "sm",
1840
+ "base",
1841
+ "lg",
1842
+ "xl",
1843
+ "2xl",
1844
+ "3xl",
1845
+ "4xl",
1846
+ "5xl",
1847
+ "6xl",
1848
+ "7xl",
1849
+ "8xl",
1850
+ "9xl",
1851
+ ];
1837
1852
  /**
1838
- * Generates a random width percentage string for skeleton loading components.
1853
+ * Extract the size key from a text size string (e.g., "text-base" "base").
1839
1854
  *
1840
- * @param {object} params - The parameter object
1841
- * @param {number} params.min - Minimum percentage value (e.g., 30 for 30%)
1842
- * @param {number} params.max - Maximum percentage value (e.g., 80 for 80%)
1843
- * @returns {string} A percentage string (e.g., "65%")
1855
+ * @param value - The text size string to parse
1856
+ * @returns {fontSizeKeys | null} The extracted size key or null if invalid
1844
1857
  */
1845
- const getResponsiveRandomWidthPercentage = ({ min, max }) => {
1846
- const randomWidth = Math.floor(Math.random() * (max - min + 1)) + min;
1847
- return `${randomWidth}%`;
1858
+ const extractSizeKey = (value) => {
1859
+ if (!value.startsWith("text-")) {
1860
+ return null;
1861
+ }
1862
+ const sizeKey = value.replace("text-", "");
1863
+ return VALID_SIZE_KEYS.find(key => key === sizeKey) ?? null;
1864
+ };
1865
+ /**
1866
+ * Calculate the height value based on the height prop and variant.
1867
+ *
1868
+ * @param height - The height value (number, CSS length, or text size key)
1869
+ * @param variant - The skeleton variant ("text" or "block")
1870
+ * @returns {string} The calculated CSS height value
1871
+ */
1872
+ const getHeightValue = (height, variant) => {
1873
+ if (typeof height === "number") {
1874
+ return `${height}px`;
1875
+ }
1876
+ const sizeKey = extractSizeKey(height);
1877
+ if (sizeKey) {
1878
+ // Text variant: use font-size × 0.7 to approximate cap-height
1879
+ // Block variant: use full line-height (for when text size keys are passed to block variant)
1880
+ return `calc(var(--font-size-${sizeKey}) * 0.7)` ;
1881
+ }
1882
+ return height;
1883
+ };
1884
+ /**
1885
+ * Calculate the vertical margin value for text variant to align with text baseline.
1886
+ * Formula: (line-height - cap-height) / 2, where cap-height = font-size × 0.7
1887
+ *
1888
+ * @param height - The height value (number, CSS length, or text size key)
1889
+ * @returns {string} The calculated CSS margin value
1890
+ */
1891
+ const getMarginValue = (height) => {
1892
+ if (typeof height === "string") {
1893
+ const sizeKey = extractSizeKey(height);
1894
+ if (sizeKey) {
1895
+ // margin = (line-height - cap-height) / 2
1896
+ // cap-height = font-size × 0.7
1897
+ // For large text sizes, this may be negative, which matches how actual text extends beyond its line-height box
1898
+ return `calc((var(--line-height-${sizeKey}) - var(--font-size-${sizeKey}) * 0.7) / 2)`;
1899
+ }
1900
+ }
1901
+ return "0";
1848
1902
  };
1849
1903
 
1850
- const cvaSkeletonContainer = cssClassVarianceUtilities.cvaMerge(["flex", "flex-col"]);
1851
- const cvaSkeletonLine = cssClassVarianceUtilities.cvaMerge([
1904
+ const cvaSkeleton = cssClassVarianceUtilities.cvaMerge([
1852
1905
  "relative",
1853
1906
  "overflow-hidden",
1854
1907
  "rounded-lg",
@@ -1875,32 +1928,28 @@ const cvaSkeletonLine = cssClassVarianceUtilities.cvaMerge([
1875
1928
  ]);
1876
1929
 
1877
1930
  /**
1878
- * Display placeholder lines before the data gets loaded to reduce load-time frustration.
1879
- * All width values are automatically constrained using CSS min() function to prevent overflow.
1931
+ * Display a single placeholder line for text content before data gets loaded to reduce load-time frustration.
1932
+ *
1933
+ * Reduces height and adds vertical margins to match the visual space text occupies within its line-height.
1934
+ * Uses text-size keys (text-xs, text-sm, text-base, etc.) for height to match actual text elements.
1935
+ *
1936
+ * For multiple text lines, use SkeletonLines component instead.
1937
+ * For shape-based elements (images, badges, buttons), use SkeletonBlock component instead.
1880
1938
  */
1881
- const SkeletonLines = react.memo(({ lines = 1, height = "0.75rem", width = "100%", gap = 10, maxWidth = "100%", className, "data-testid": dataTestId, }) => {
1882
- const gapStyle = typeof gap === "number" ? `${gap}px` : gap;
1883
- return (jsxRuntime.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) => (jsxRuntime.jsx("div", { className: cvaSkeletonLine(), "data-testid": dataTestId ? `${dataTestId}-${index}` : `skeleton-lines-${index}`, "data-type": "loading-skeleton-line", style: {
1884
- width: getDimension({ dimension: width, index, direction: "width", maxWidth }),
1885
- height: getDimension({ dimension: height, index, direction: "height" }),
1886
- } }, index))) }));
1939
+ const SkeletonLabel = react.memo((props) => {
1940
+ const { width = "100%", textSize = "text-base", flexibleWidth = true, className, "data-testid": dataTestId, children, } = props;
1941
+ const widthValue = typeof width === "number" ? `${width}px` : width;
1942
+ const heightValue = getHeightValue(textSize);
1943
+ const marginValue = getMarginValue(textSize);
1944
+ return (jsxRuntime.jsx("div", { "aria-label": "Loading", className: cvaSkeleton({ className }), "data-testid": dataTestId, role: "status", style: {
1945
+ width: flexibleWidth ? "100%" : widthValue,
1946
+ maxWidth: flexibleWidth ? widthValue : undefined,
1947
+ height: heightValue,
1948
+ marginTop: marginValue,
1949
+ marginBottom: marginValue,
1950
+ }, children: children }));
1887
1951
  });
1888
- const getDimension = (params) => {
1889
- const { dimension, index } = params;
1890
- let value;
1891
- if (Array.isArray(dimension)) {
1892
- const dimValue = dimension[index] ?? dimension[0] ?? "100%";
1893
- value = typeof dimValue === "number" ? `${dimValue}px` : dimValue;
1894
- }
1895
- else {
1896
- value = typeof dimension === "number" ? `${dimension}px` : dimension;
1897
- }
1898
- // For width values, wrap in min() to ensure max container width
1899
- if (params.direction === "width") {
1900
- return `min(${value}, ${params.maxWidth})`;
1901
- }
1902
- return value;
1903
- };
1952
+ SkeletonLabel.displayName = "SkeletonLabel";
1904
1953
 
1905
1954
  const cvaContainerStyles = cssClassVarianceUtilities.cvaMerge([
1906
1955
  "flex",
@@ -1993,7 +2042,7 @@ const EmptyState = ({ description, altText, image = "SEARCH_DOCUMENT", customIma
1993
2042
  return SearchDocumentSVG;
1994
2043
  }
1995
2044
  }, [image]);
1996
- return (jsxRuntime.jsx("div", { className: cvaContainerStyles({ className }), "data-testid": dataTestId ?? "empty-state", children: loading ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(Spinner, { centering: "centered", "data-testid": "spinner" }), jsxRuntime.jsx(SkeletonLines, { "data-testid": "skeleton-lines", width: 50 })] })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [customImageSrc !== null && customImageSrc !== undefined ? (typeof customImageSrc === "string" ? (jsxRuntime.jsx("img", { alt: altText, className: cvaImgStyles(), height: 200, src: customImageSrc, width: 200 })) : (customImageSrc)) : (typeof ImageSource !== "undefined" && (jsxRuntime.jsx(ImageSource, { "data-testid": "empty-state-image", height: 200, width: 200 }, image))), description !== undefined && description !== "" ? (jsxRuntime.jsx(Text, { align: "center", size: "large", children: description })) : null, jsxRuntime.jsxs("div", { className: "mt-4 grid gap-3", children: [jsxRuntime.jsxs("div", { className: "flex gap-3", children: [secondaryAction ? (jsxRuntime.jsx(Button, { "data-testid": "empty-state-secondary-button", disabled: secondaryAction.disabled, onClick: secondaryAction.onClick, variant: "secondary", children: secondaryAction.to ? (jsxRuntime.jsx(reactRouter.Link, { params: secondaryAction.to.parameters, to: secondaryAction.to.pathname, children: secondaryAction.title })) : (secondaryAction.title) })) : null, primaryAction ? (jsxRuntime.jsx(Button, { "data-testid": "empty-state-primary-button", disabled: primaryAction.disabled, onClick: primaryAction.onClick, children: primaryAction.to ? (jsxRuntime.jsx(reactRouter.Link, { params: primaryAction.to.parameters, to: primaryAction.to.pathname, children: primaryAction.title })) : (primaryAction.title) })) : null] }), additionalHelpAction?.to ? (jsxRuntime.jsx(Button, { asChild: true, "data-testid": "empty-state-additional-button", disabled: additionalHelpAction.disabled, onClick: additionalHelpAction.onClick, suffix: jsxRuntime.jsx(Icon, { name: "ArrowTopRightOnSquare", size: "small" }), variant: "ghost", children: jsxRuntime.jsx(reactRouter.Link, { params: additionalHelpAction.to.parameters, target: additionalHelpAction.to.target, to: additionalHelpAction.to.pathname, children: additionalHelpAction.title }) })) : null] })] })) }));
2045
+ return (jsxRuntime.jsx("div", { className: cvaContainerStyles({ className }), "data-testid": dataTestId ?? "empty-state", children: loading ? (jsxRuntime.jsxs("div", { className: "flex w-full flex-col items-center gap-4", children: [jsxRuntime.jsx(Spinner, { centering: "centered", "data-testid": "spinner" }), jsxRuntime.jsx(SkeletonLabel, { textSize: "text-base", width: "clamp(20%, 200px, 80%)" })] })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [customImageSrc !== null && customImageSrc !== undefined ? (typeof customImageSrc === "string" ? (jsxRuntime.jsx("img", { alt: altText, className: cvaImgStyles(), height: 200, src: customImageSrc, width: 200 })) : (customImageSrc)) : (typeof ImageSource !== "undefined" && (jsxRuntime.jsx(ImageSource, { "data-testid": "empty-state-image", height: 200, width: 200 }, image))), description !== undefined && description !== "" ? (jsxRuntime.jsx(Text, { align: "center", size: "large", children: description })) : null, jsxRuntime.jsxs("div", { className: "mt-4 grid gap-3", children: [jsxRuntime.jsxs("div", { className: "flex gap-3", children: [secondaryAction ? (jsxRuntime.jsx(Button, { "data-testid": "empty-state-secondary-button", disabled: secondaryAction.disabled, onClick: secondaryAction.onClick, variant: "secondary", children: secondaryAction.to ? (jsxRuntime.jsx(reactRouter.Link, { params: secondaryAction.to.parameters, to: secondaryAction.to.pathname, children: secondaryAction.title })) : (secondaryAction.title) })) : null, primaryAction ? (jsxRuntime.jsx(Button, { "data-testid": "empty-state-primary-button", disabled: primaryAction.disabled, onClick: primaryAction.onClick, children: primaryAction.to ? (jsxRuntime.jsx(reactRouter.Link, { params: primaryAction.to.parameters, to: primaryAction.to.pathname, children: primaryAction.title })) : (primaryAction.title) })) : null] }), additionalHelpAction?.to ? (jsxRuntime.jsx(Button, { asChild: true, "data-testid": "empty-state-additional-button", disabled: additionalHelpAction.disabled, onClick: additionalHelpAction.onClick, suffix: jsxRuntime.jsx(Icon, { name: "ArrowTopRightOnSquare", size: "small" }), variant: "ghost", children: jsxRuntime.jsx(reactRouter.Link, { params: additionalHelpAction.to.parameters, target: additionalHelpAction.to.target, to: additionalHelpAction.to.pathname, children: additionalHelpAction.title }) })) : null] })] })) }));
1997
2046
  };
1998
2047
 
1999
2048
  const cvaEmptyValue = cssClassVarianceUtilities.cvaMerge(["text-neutral-400"]);
@@ -3932,9 +3981,189 @@ const cvaKPITrendPercentage = cssClassVarianceUtilities.cvaMerge([""], {
3932
3981
  * @param {KPIProps} props - The props for the KPI component
3933
3982
  * @returns {ReactElement} KPI component
3934
3983
  */
3935
- const KPI = ({ title, value, loading = false, unit, className, "data-testid": dataTestId, tooltipLabel, variant = "default", style, ...rest }) => {
3984
+ const KPI = ({ title, value, unit, className, "data-testid": dataTestId, tooltipLabel, variant = "default", style, ...rest }) => {
3936
3985
  const isSmallVariant = variant === "small";
3937
- 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] })) })] }) }));
3986
+ 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: [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: 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] }) })] }) }));
3987
+ };
3988
+
3989
+ /**
3990
+ * Display a single placeholder block for shape-based elements before data gets loaded to reduce load-time frustration.
3991
+ *
3992
+ * Fills the full height for images, badges, buttons, avatars, and other shape-based elements.
3993
+ * Uses numbers or CSS length values for height.
3994
+ *
3995
+ * For text content, use SkeletonLabel component instead.
3996
+ * For multiple text lines, use SkeletonLines component instead.
3997
+ */
3998
+ const SkeletonBlock = react.memo((props) => {
3999
+ const { width = "100%", height = 16, flexibleWidth = false, className, "data-testid": dataTestId, children } = props;
4000
+ const widthValue = typeof width === "number" ? `${width}px` : width;
4001
+ const heightValue = typeof height === "number" ? `${height}px` : height;
4002
+ return (jsxRuntime.jsx("div", { "aria-label": "Loading", className: cvaSkeleton({ className }), "data-testid": dataTestId, role: "status", style: {
4003
+ width: flexibleWidth ? "100%" : widthValue,
4004
+ maxWidth: flexibleWidth ? widthValue : undefined,
4005
+ height: heightValue,
4006
+ }, children: children }));
4007
+ });
4008
+ SkeletonBlock.displayName = "SkeletonBlock";
4009
+
4010
+ /**
4011
+ * Generates a random width percentage string for skeleton loading components.
4012
+ *
4013
+ * @param {object} params - The parameter object
4014
+ * @param {number} params.min - Minimum percentage value (e.g., 30 for 30%)
4015
+ * @param {number} params.max - Maximum percentage value (e.g., 80 for 80%)
4016
+ * @returns {string} A percentage string (e.g., "65%")
4017
+ */
4018
+ const getResponsiveRandomWidthPercentage = ({ min, max }) => {
4019
+ const randomWidth = Math.floor(Math.random() * (max - min + 1)) + min;
4020
+ return `${randomWidth}%`;
4021
+ };
4022
+
4023
+ /**
4024
+ * Preset configurations for common skeleton patterns.
4025
+ */
4026
+ const PRESET_CONFIGURATIONS = {
4027
+ "short-paragraph": [
4028
+ { textSize: "text-base", width: "100%" },
4029
+ { textSize: "text-base", width: "100%" },
4030
+ { textSize: "text-base", width: "60%" },
4031
+ ],
4032
+ paragraph: [
4033
+ { textSize: "text-base", width: "100%" },
4034
+ { textSize: "text-base", width: "100%" },
4035
+ { textSize: "text-base", width: "95%" },
4036
+ { textSize: "text-base", width: "90%" },
4037
+ { textSize: "text-base", width: "65%" },
4038
+ ],
4039
+ "long-paragraph": [
4040
+ { textSize: "text-base", width: "100%" },
4041
+ { textSize: "text-base", width: "100%" },
4042
+ { textSize: "text-base", width: "98%" },
4043
+ { textSize: "text-base", width: "95%" },
4044
+ { textSize: "text-base", width: "92%" },
4045
+ { textSize: "text-base", width: "88%" },
4046
+ { textSize: "text-base", width: "85%" },
4047
+ { textSize: "text-base", width: "60%" },
4048
+ ],
4049
+ "title-paragraph": [
4050
+ { textSize: "text-2xl", width: "65%" },
4051
+ { textSize: "text-base", width: "100%" },
4052
+ { textSize: "text-base", width: "95%" },
4053
+ { textSize: "text-base", width: "70%" },
4054
+ ],
4055
+ "heading-text": [
4056
+ { textSize: "text-xl", width: "60%" },
4057
+ { textSize: "text-base", width: "100%" },
4058
+ { textSize: "text-base", width: "100%" },
4059
+ { textSize: "text-base", width: "90%" },
4060
+ { textSize: "text-base", width: "65%" },
4061
+ ],
4062
+ article: [
4063
+ { textSize: "text-3xl", width: "70%" },
4064
+ { textSize: "text-sm", width: "40%" },
4065
+ { textSize: "text-base", width: "100%" },
4066
+ { textSize: "text-base", width: "100%" },
4067
+ { textSize: "text-base", width: "95%" },
4068
+ { textSize: "text-base", width: "90%" },
4069
+ { textSize: "text-base", width: "65%" },
4070
+ ],
4071
+ };
4072
+
4073
+ const cvaSkeletonContainer = cssClassVarianceUtilities.cvaMerge(["flex", "flex-col"]);
4074
+ cssClassVarianceUtilities.cvaMerge([
4075
+ "relative",
4076
+ "overflow-hidden",
4077
+ "rounded-lg",
4078
+ // Gradient background
4079
+ "bg-gradient-to-r",
4080
+ "from-gray-200/80",
4081
+ "via-gray-300/60",
4082
+ "to-gray-200/80",
4083
+ // Pulse animation
4084
+ "animate-pulse",
4085
+ // Shimmer overlay
4086
+ "before:absolute",
4087
+ "before:inset-0",
4088
+ "before:bg-gradient-to-r",
4089
+ "before:from-transparent",
4090
+ "before:via-white/50",
4091
+ "before:to-transparent",
4092
+ "before:opacity-0",
4093
+ "before:animate-pulse",
4094
+ // Smooth transitions for accessibility
4095
+ "transition-all",
4096
+ "duration-300",
4097
+ "ease-in-out",
4098
+ ]);
4099
+
4100
+ /**
4101
+ * Display multiple placeholder lines before data gets loaded to reduce load-time frustration.
4102
+ *
4103
+ * Supports three modes:
4104
+ * - **Uniform mode** (default): Display identical lines using `count` prop
4105
+ * - **Custom mode**: Display customized lines with per-line configuration using `variant="custom"` and `lines` prop
4106
+ * - **Preset mode**: Display common patterns using `variant="preset"` and `preset` name
4107
+ *
4108
+ * Built on top of the [SkeletonLabel](?path=/docs/components-loading-states-skeletonlabel--docs) component for text-specific margins and sizing.
4109
+ *
4110
+ * @example
4111
+ * // Uniform lines (simple mode)
4112
+ * <SkeletonLines count={3} textSize="text-base" variant="uniform" />
4113
+ * @example
4114
+ * // Custom lines (advanced mode)
4115
+ * <SkeletonLines
4116
+ * variant="custom"
4117
+ * lines={[
4118
+ * { textSize: "text-lg", width: "100%" },
4119
+ * { textSize: "text-base", width: "95%" },
4120
+ * { textSize: "text-base", width: "70%" }
4121
+ * ]}
4122
+ * />
4123
+ * @example
4124
+ * // Preset lines (quick patterns)
4125
+ * <SkeletonLines variant="preset" preset="title-paragraph" />
4126
+ */
4127
+ const SkeletonLines = react.memo((props) => {
4128
+ const { className, "data-testid": dataTestId } = props;
4129
+ // Generate line configs based on variant
4130
+ let lineConfigs;
4131
+ if (props.variant === "preset") {
4132
+ // TypeScript now knows props is PresetSkeletonLinesProps
4133
+ lineConfigs = PRESET_CONFIGURATIONS[props.preset];
4134
+ }
4135
+ else if (props.variant === "custom") {
4136
+ // TypeScript now knows props is CustomSkeletonLinesProps
4137
+ lineConfigs = props.lines;
4138
+ }
4139
+ else {
4140
+ // TypeScript now knows props is UniformSkeletonLinesProps
4141
+ lineConfigs = Array.from({ length: props.count }, () => ({
4142
+ textSize: props.textSize,
4143
+ width: props.width,
4144
+ flexibleWidth: props.flexibleWidth,
4145
+ className: props.lineClassName,
4146
+ }));
4147
+ }
4148
+ const lineCount = lineConfigs.length;
4149
+ return (jsxRuntime.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) => (jsxRuntime.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))) }));
4150
+ });
4151
+ SkeletonLines.displayName = "SkeletonLines";
4152
+
4153
+ /**
4154
+ * Skeleton loading indicator that mimics the KPI component structure.
4155
+ * Uses the same layout, spacing, and visual hierarchy as KPI.
4156
+ */
4157
+ const KPISkeleton = ({ variant = "default", className, "data-testid": dataTestId, style, ...rest }) => {
4158
+ const isSmallVariant = variant === "small";
4159
+ // Generate stable random widths once and never change them
4160
+ const lineWidths = react.useMemo(() => {
4161
+ return {
4162
+ title: getResponsiveRandomWidthPercentage({ min: 60, max: 85 }),
4163
+ value: getResponsiveRandomWidthPercentage({ min: 70, max: 100 }),
4164
+ };
4165
+ }, []);
4166
+ return (jsxRuntime.jsxs("div", { className: cvaKPI({ variant, className }), "data-testid": dataTestId, style: style, ...rest, children: [jsxRuntime.jsx("div", { className: tailwindMerge.twMerge("flex", "items-center", "flex-row", isSmallVariant ? "h-4" : "h-5"), children: jsxRuntime.jsx(SkeletonLabel, { "data-testid": dataTestId ? `${dataTestId}-title-loading` : undefined, textSize: isSmallVariant ? "text-xs" : "text-sm", width: lineWidths.title }) }), jsxRuntime.jsx("div", { className: tailwindMerge.twMerge("truncate", "whitespace-nowrap", "flex h-7 flex-row items-center"), children: jsxRuntime.jsx(SkeletonLabel, { "data-testid": dataTestId ? `${dataTestId}-value-loading` : undefined, textSize: isSmallVariant ? "text-xs" : "text-lg", width: lineWidths.value }) })] }));
3938
4167
  };
3939
4168
 
3940
4169
  /**
@@ -4090,6 +4319,8 @@ const cvaKPICard = cssClassVarianceUtilities.cvaMerge([
4090
4319
  isActive: false,
4091
4320
  },
4092
4321
  });
4322
+ const cvaKPICardBody = cssClassVarianceUtilities.cvaMerge(["grid", "gap-2", "px-3", "pb-2", "pt-3"]);
4323
+ const cvaKPICardHeader = cssClassVarianceUtilities.cvaMerge(["grid", "grid-cols-[1fr_auto]", "justify-between", "gap-2"]);
4093
4324
  const cvaKPIIconContainer = cssClassVarianceUtilities.cvaMerge(["flex", "items-center", "justify-center", "w-7", "h-7", "rounded-md", "text-white"], {
4094
4325
  variants: {
4095
4326
  iconColor: {
@@ -4108,15 +4339,23 @@ const cvaKPIIconContainer = cssClassVarianceUtilities.cvaMerge(["flex", "items-c
4108
4339
  * @param {KPICardProps} props - The props for the KPICard component
4109
4340
  * @returns {ReactElement} KPICard component
4110
4341
  */
4111
- const KPICard = ({ isActive = false, onClick, className, "data-testid": dataTestId, children, iconName = undefined, iconColor = "info", loading = false, notice, valueBar, trends, unit, ...rest }) => {
4112
- const isClickable = Boolean(onClick !== undefined && loading !== true);
4342
+ const KPICard = ({ isActive = false, onClick, className, "data-testid": dataTestId, children, iconName = undefined, iconColor = "info", notice, valueBar, trends, unit, ...rest }) => {
4343
+ const isClickable = Boolean(onClick !== undefined);
4113
4344
  return (jsxRuntime.jsx(Card, { className: cvaKPICard({
4114
4345
  isClickable,
4115
4346
  isActive,
4116
4347
  className,
4117
- }), "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%" })) : (
4348
+ }), "data-testid": dataTestId ? dataTestId : undefined, onClick: onClick, children: jsxRuntime.jsxs(CardBody, { className: cvaKPICardBody(), gap: "none", padding: "none", children: [jsxRuntime.jsxs("div", { className: cvaKPICardHeader(), children: [jsxRuntime.jsx(KPI, { ...rest, className: "p-0", "data-testid": dataTestId ? `${dataTestId}-kpi` : undefined, 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 ? (jsxRuntime.jsx(TrendIndicators, { "data-testid": dataTestId ? `${dataTestId}-trend-indicators` : undefined, trends: trends })) : null, valueBar !== undefined ? (jsxRuntime.jsx(ValueBar, { className: "h-2", "data-testid": dataTestId ? `${dataTestId}-value-bar` : undefined, ...valueBar })) : null, notice !== undefined ? (
4118
4349
  // NOTE: Can't use Notice component here due to the non-flexible text styling options
4119
- 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] }) }));
4350
+ 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] }) }));
4351
+ };
4352
+
4353
+ /**
4354
+ * Skeleton loading indicator that mimics the KPICard component structure.
4355
+ * Uses the same layout, spacing, and visual hierarchy as KPICard.
4356
+ */
4357
+ const KPICardSkeleton = ({ hasIcon = false, hasTrends = false, hasValueBar = false, hasNotice = false, children, className, "data-testid": dataTestId, style, ...rest }) => {
4358
+ return (jsxRuntime.jsx(Card, { className: cvaKPICard({ className }), "data-testid": dataTestId, style: style, ...rest, children: jsxRuntime.jsxs(CardBody, { className: cvaKPICardBody(), gap: "none", padding: "none", children: [jsxRuntime.jsxs("div", { className: cvaKPICardHeader(), children: [jsxRuntime.jsx(KPISkeleton, { className: "p-0", "data-testid": dataTestId ? `${dataTestId}-kpi` : undefined }), hasIcon ? (jsxRuntime.jsx(SkeletonBlock, { "data-testid": dataTestId ? `${dataTestId}-icon-loading` : undefined, height: 28, width: 28 })) : null] }), hasTrends ? (jsxRuntime.jsx(SkeletonLabel, { "data-testid": dataTestId ? `${dataTestId}-trend-indicator-loading` : undefined, textSize: "text-xs", width: "100%" })) : null, hasValueBar ? (jsxRuntime.jsx(SkeletonLabel, { "data-testid": dataTestId ? `${dataTestId}-value-bar-loading` : undefined, textSize: "text-xs", width: "100%" })) : null, hasNotice ? (jsxRuntime.jsx(SkeletonLabel, { "data-testid": dataTestId ? `${dataTestId}-notice-loading` : undefined, textSize: "text-xs", width: "100%" })) : null, children] }) }));
4120
4359
  };
4121
4360
 
4122
4361
  const cvaListContainer = cssClassVarianceUtilities.cvaMerge(["overflow-y-auto", "overflow-x-hidden", "h-full"], {
@@ -4159,124 +4398,6 @@ const cvaListItem$1 = cssClassVarianceUtilities.cvaMerge(["absolute", "top-0", "
4159
4398
  },
4160
4399
  });
4161
4400
 
4162
- const cvaSkeleton = cssClassVarianceUtilities.cvaMerge([
4163
- "relative",
4164
- "overflow-hidden",
4165
- "rounded-lg",
4166
- // Gradient background
4167
- "bg-gradient-to-r",
4168
- "from-gray-200/80",
4169
- "via-gray-300/60",
4170
- "to-gray-200/80",
4171
- // Pulse animation
4172
- "animate-pulse",
4173
- // Shimmer overlay
4174
- "before:absolute",
4175
- "before:inset-0",
4176
- "before:bg-gradient-to-r",
4177
- "before:from-transparent",
4178
- "before:via-white/50",
4179
- "before:to-transparent",
4180
- "before:opacity-0",
4181
- "before:animate-pulse",
4182
- // Smooth transitions for accessibility
4183
- "transition-all",
4184
- "duration-300",
4185
- "ease-in-out",
4186
- ]);
4187
-
4188
- const VALID_SIZE_KEYS = [
4189
- "xs",
4190
- "sm",
4191
- "base",
4192
- "lg",
4193
- "xl",
4194
- "2xl",
4195
- "3xl",
4196
- "4xl",
4197
- "5xl",
4198
- "6xl",
4199
- "7xl",
4200
- "8xl",
4201
- "9xl",
4202
- ];
4203
- /**
4204
- * Extract the size key from a text size string (e.g., "text-base" → "base").
4205
- *
4206
- * @param value - The text size string to parse
4207
- * @returns {fontSizeKeys | null} The extracted size key or null if invalid
4208
- */
4209
- const extractSizeKey = (value) => {
4210
- if (!value.startsWith("text-")) {
4211
- return null;
4212
- }
4213
- const sizeKey = value.replace("text-", "");
4214
- return VALID_SIZE_KEYS.find(key => key === sizeKey) ?? null;
4215
- };
4216
- /**
4217
- * Calculate the height value based on the height prop and variant.
4218
- *
4219
- * @param height - The height value (number, CSS length, or text size key)
4220
- * @param variant - The skeleton variant ("text" or "block")
4221
- * @returns {string} The calculated CSS height value
4222
- */
4223
- const getHeightValue = (height, variant) => {
4224
- if (typeof height === "number") {
4225
- return `${height}px`;
4226
- }
4227
- const sizeKey = extractSizeKey(height);
4228
- if (sizeKey) {
4229
- // Text variant: use font-size × 0.7 to approximate cap-height
4230
- // Block variant: use full line-height (for when text size keys are passed to block variant)
4231
- return variant === "text" ? `calc(var(--font-size-${sizeKey}) * 0.7)` : `var(--line-height-${sizeKey})`;
4232
- }
4233
- return height;
4234
- };
4235
- /**
4236
- * Calculate the vertical margin value for text variant to align with text baseline.
4237
- * Formula: (line-height - cap-height) / 2, where cap-height = font-size × 0.7
4238
- *
4239
- * @param height - The height value (number, CSS length, or text size key)
4240
- * @returns {string} The calculated CSS margin value
4241
- */
4242
- const getMarginValue = (height) => {
4243
- if (typeof height === "string") {
4244
- const sizeKey = extractSizeKey(height);
4245
- if (sizeKey) {
4246
- // margin = (line-height - cap-height) / 2
4247
- // cap-height = font-size × 0.7
4248
- // For large text sizes, this may be negative, which matches how actual text extends beyond its line-height box
4249
- return `calc((var(--line-height-${sizeKey}) - var(--font-size-${sizeKey}) * 0.7) / 2)`;
4250
- }
4251
- }
4252
- return "0";
4253
- };
4254
- /**
4255
- * Display a single placeholder line before data gets loaded to reduce load-time frustration.
4256
- *
4257
- * Use `variant="text"` with text-size keys for text placeholders.
4258
- * Use `variant="block"` with numbers/CSS for images, badges, buttons, and other shape-based elements.
4259
- * Pass children to create custom skeleton layouts.
4260
- */
4261
- const Skeleton = react.memo((props) => {
4262
- const { width = "100%", className, "data-testid": dataTestId, children } = props;
4263
- const variant = props.variant ?? "text";
4264
- const height = props.height ?? (variant === "text" ? "text-base" : 16);
4265
- const flexibleWidth = props.flexibleWidth ?? variant === "text";
4266
- const widthValue = typeof width === "number" ? `${width}px` : width;
4267
- const isTextVariant = variant === "text";
4268
- const heightValue = getHeightValue(height, variant);
4269
- const marginValue = isTextVariant ? getMarginValue(height) : undefined;
4270
- return (jsxRuntime.jsx("div", { "aria-label": "Loading", className: cvaSkeleton({ className }), "data-testid": dataTestId, role: "status", style: {
4271
- width: flexibleWidth ? "100%" : widthValue,
4272
- maxWidth: flexibleWidth ? widthValue : undefined,
4273
- height: heightValue,
4274
- marginTop: marginValue,
4275
- marginBottom: marginValue,
4276
- }, children: children }));
4277
- });
4278
- Skeleton.displayName = "Skeleton";
4279
-
4280
4401
  const cvaListItem = cssClassVarianceUtilities.cvaMerge(["py-3", "px-4", "min-h-14", "w-full", "flex", "justify-between", "items-center"]);
4281
4402
  const cvaMainInformationClass = cssClassVarianceUtilities.cvaMerge(["grid", "items-center", "text-sm", "gap-2"], {
4282
4403
  variants: {
@@ -4325,7 +4446,7 @@ const ListItemSkeleton = ({ hasThumbnail = DEFAULT_SKELETON_LIST_ITEM_PROPS.hasT
4325
4446
  details: getResponsiveRandomWidthPercentage({ min: 25, max: 45 }),
4326
4447
  };
4327
4448
  }, []);
4328
- return (jsxRuntime.jsxs("div", { className: cvaListItem({ className: "w-full" }), children: [jsxRuntime.jsxs("div", { className: cvaMainInformationClass({ hasThumbnail, className: "w-full" }), children: [hasThumbnail ? (jsxRuntime.jsx("div", { className: cvaThumbnailContainer({ className: "bg-gray-200" }), children: jsxRuntime.jsx("div", { className: tailwindMerge.twMerge("bg-gray-300", thumbnailShape === "circle" ? "rounded-full" : "rounded"), style: { width: 20, height: 20 } }) })) : null, jsxRuntime.jsxs("div", { className: cvaTextContainer(), children: [jsxRuntime.jsx("div", { className: cvaTitleRow(), children: jsxRuntime.jsx(Skeleton, { height: "text-sm", width: lineWidths.title }) }), hasDescription ? (jsxRuntime.jsx("div", { className: cvaDescriptionRow(), children: jsxRuntime.jsx(Skeleton, { height: "text-xs", width: lineWidths.description }) })) : null, hasMeta ? (jsxRuntime.jsx("div", { className: cvaMetaRow(), children: jsxRuntime.jsx(Skeleton, { height: "text-xs", width: lineWidths.meta }) })) : null] })] }), hasDetails ? (jsxRuntime.jsx("div", { className: cvaDetailsContainer(), children: jsxRuntime.jsx(Skeleton, { height: "text-sm", width: lineWidths.details }) })) : null] }));
4449
+ return (jsxRuntime.jsxs("div", { className: cvaListItem({ className: "w-full" }), children: [jsxRuntime.jsxs("div", { className: cvaMainInformationClass({ hasThumbnail, className: "w-full" }), children: [hasThumbnail ? (jsxRuntime.jsx("div", { className: cvaThumbnailContainer({ className: "bg-gray-200" }), children: jsxRuntime.jsx("div", { className: tailwindMerge.twMerge("bg-gray-300", thumbnailShape === "circle" ? "rounded-full" : "rounded"), style: { width: 20, height: 20 } }) })) : null, jsxRuntime.jsxs("div", { className: cvaTextContainer(), children: [jsxRuntime.jsx("div", { className: cvaTitleRow(), children: jsxRuntime.jsx(SkeletonLabel, { textSize: "text-sm", width: lineWidths.title }) }), hasDescription ? (jsxRuntime.jsx("div", { className: cvaDescriptionRow(), children: jsxRuntime.jsx(SkeletonLabel, { textSize: "text-xs", width: lineWidths.description }) })) : null, hasMeta ? (jsxRuntime.jsx("div", { className: cvaMetaRow(), children: jsxRuntime.jsx(SkeletonLabel, { textSize: "text-xs", width: lineWidths.meta }) })) : null] })] }), hasDetails ? (jsxRuntime.jsx("div", { className: cvaDetailsContainer(), children: jsxRuntime.jsx(SkeletonLabel, { textSize: "text-sm", width: lineWidths.details }) })) : null] }));
4329
4450
  };
4330
4451
 
4331
4452
  /**
@@ -5232,9 +5353,12 @@ const PageContent = ({ className, children, "data-testid": dataTestId, layout, }
5232
5353
  return (jsxRuntime.jsx("div", { className: cvaPageContent({ className, layout }), "data-testid": dataTestId ? dataTestId : "page-content", children: children }));
5233
5354
  };
5234
5355
 
5235
- const LoadingContent = () => (jsxRuntime.jsx("div", { className: "flex 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: [16, 25], lines: 2, width: [50, 40] }) }) }));
5356
+ const LoadingContent = () => (jsxRuntime.jsx("div", { className: "flex flex-row items-center", "data-testid": "kpi-card-loading-content", children: jsxRuntime.jsx("div", { className: "w-full", children: jsxRuntime.jsx(SkeletonLines, { lines: [
5357
+ { textSize: "text-xs", width: 50 },
5358
+ { textSize: "text-lg", width: 40 },
5359
+ ], variant: "custom" }) }) }));
5236
5360
  /**
5237
- * The PageHeaderKpiMetrics component is used to render the KPI metrics in the PageHeader component.
5361
+ * Renders KPI metrics in the PageHeader component.
5238
5362
  *
5239
5363
  * @param {object} props - The props for the PageHeaderKpiMetrics component
5240
5364
  * @param {Array<PageHeaderKpiMetricsType>} props.kpiMetrics - The KPI metrics to render
@@ -5473,7 +5597,7 @@ const Pagination = ({ previousPage, nextPage, canPreviousPage = false, canNextPa
5473
5597
  onPageChange?.({ from, to, description });
5474
5598
  }, [page, onPageChange, previousPage, nextPage]);
5475
5599
  if (loading) {
5476
- return (jsxRuntime.jsx("div", { className: cvaPagination({ className }), children: jsxRuntime.jsx(SkeletonLines, { height: 16, width: 150 }) }));
5600
+ return (jsxRuntime.jsx("div", { className: cvaPagination({ className }), children: jsxRuntime.jsx(SkeletonLabel, { textSize: "text-sm", width: 150 }) }));
5477
5601
  }
5478
5602
  return (jsxRuntime.jsxs("div", { className: cvaPagination({ className }), "data-testid": dataTestId, children: [jsxRuntime.jsx(IconButton, { "data-testid": "prev-page", disabled: cursorBase ? !canPreviousPage || false : page !== undefined && page <= 0, icon: jsxRuntime.jsx(Icon, { name: "ChevronLeft", size: "small" }), onClick: () => handlePageChange("prev"), size: "small", variant: "ghost-neutral" }), !cursorBase && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("div", { className: cvaPaginationText(), "data-testid": "current-page", children: currentPage }), jsxRuntime.jsx("div", { className: cvaPaginationText(), "data-testid": "page-count", children: pageCount !== null && pageCount !== undefined && getTranslatedCount ? getTranslatedCount(pageCount) : null })] })), jsxRuntime.jsx(IconButton, { "data-testid": "next-page", disabled: cursorBase
5479
5603
  ? !canNextPage || false
@@ -5712,7 +5836,7 @@ const PreferenceCardSkeleton = ({ hasIcon = DEFAULT_SKELETON_PREFERENCE_CARD_PRO
5712
5836
  description: getRandomWidth(160, 240),
5713
5837
  };
5714
5838
  }, []);
5715
- return (jsxRuntime.jsxs("div", { className: cvaPreferenceCard(), children: [hasInput ? (jsxRuntime.jsx("div", { className: cvaInputContainer({ itemPlacement: "center" }), children: jsxRuntime.jsx(Skeleton, { height: 20, variant: "block", width: 20 }) })) : null, jsxRuntime.jsx("div", { className: cvaContentWrapper(), children: jsxRuntime.jsx(GridAreas, { ...gridAreas, className: cvaContentContainer({ itemPlacement: "center" }), children: slots => (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [hasIcon ? (jsxRuntime.jsx("div", { ...slots.icon, children: jsxRuntime.jsx(Skeleton, { className: cvaIconBackground({ disabled: false }), height: 32, variant: "block", width: 32 }) })) : null, jsxRuntime.jsxs("div", { ...slots.information, className: "min-w-0 flex-1", children: [jsxRuntime.jsx("div", { className: "grid min-w-0 grid-cols-[1fr_auto] items-center gap-2", children: jsxRuntime.jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [jsxRuntime.jsx(Skeleton, { height: "text-sm", width: lineWidths.title }), hasTitleTag ? jsxRuntime.jsx(TagSkeleton, { size: "small" }) : null] }) }), jsxRuntime.jsx(Skeleton, { height: "text-xs", width: lineWidths.description })] }), hasCardTag ? (jsxRuntime.jsx("div", { ...slots.cardTag, className: "justify-self-end", children: jsxRuntime.jsx(TagSkeleton, { size: "medium" }) })) : null] })) }) })] }));
5839
+ return (jsxRuntime.jsxs("div", { className: cvaPreferenceCard(), children: [hasInput ? (jsxRuntime.jsx("div", { className: cvaInputContainer({ itemPlacement: "center" }), children: jsxRuntime.jsx(SkeletonBlock, { height: 20, width: 20 }) })) : null, jsxRuntime.jsx("div", { className: cvaContentWrapper(), children: jsxRuntime.jsx(GridAreas, { ...gridAreas, className: cvaContentContainer({ itemPlacement: "center" }), children: slots => (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [hasIcon ? (jsxRuntime.jsx("div", { ...slots.icon, children: jsxRuntime.jsx(SkeletonBlock, { className: cvaIconBackground({ disabled: false }), height: 32, width: 32 }) })) : null, jsxRuntime.jsxs("div", { ...slots.information, className: "min-w-0 flex-1", children: [jsxRuntime.jsx("div", { className: "grid min-w-0 grid-cols-[1fr_auto] items-center gap-2", children: jsxRuntime.jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [jsxRuntime.jsx(SkeletonLabel, { textSize: "text-sm", width: lineWidths.title }), hasTitleTag ? jsxRuntime.jsx(TagSkeleton, { size: "small" }) : null] }) }), jsxRuntime.jsx(SkeletonLabel, { textSize: "text-xs", width: lineWidths.description })] }), hasCardTag ? (jsxRuntime.jsx("div", { ...slots.cardTag, className: "justify-self-end", children: jsxRuntime.jsx(TagSkeleton, { size: "medium" }) })) : null] })) }) })] }));
5716
5840
  };
5717
5841
  /**
5718
5842
  * Simple tag skeleton for use within PreferenceCardSkeleton.
@@ -5721,7 +5845,7 @@ const PreferenceCardSkeleton = ({ hasIcon = DEFAULT_SKELETON_PREFERENCE_CARD_PRO
5721
5845
  const TagSkeleton = ({ size }) => {
5722
5846
  const width = react.useMemo(() => getRandomWidth(40, 64), []);
5723
5847
  const height = size === "small" ? 18 : 24;
5724
- return jsxRuntime.jsx(Skeleton, { className: "rounded-full", height: height, variant: "block", width: width });
5848
+ return jsxRuntime.jsx(SkeletonBlock, { className: "rounded-full", height: height, width: width });
5725
5849
  };
5726
5850
 
5727
5851
  function useConfirmExit(confirmExit, when = true) {
@@ -7194,6 +7318,8 @@ exports.IconButton = IconButton;
7194
7318
  exports.Indicator = Indicator;
7195
7319
  exports.KPI = KPI;
7196
7320
  exports.KPICard = KPICard;
7321
+ exports.KPICardSkeleton = KPICardSkeleton;
7322
+ exports.KPISkeleton = KPISkeleton;
7197
7323
  exports.List = List;
7198
7324
  exports.ListItem = ListItem;
7199
7325
  exports.MenuDivider = MenuDivider;
@@ -7221,7 +7347,8 @@ exports.Prompt = Prompt;
7221
7347
  exports.ROLE_CARD = ROLE_CARD;
7222
7348
  exports.SectionHeader = SectionHeader;
7223
7349
  exports.Sidebar = Sidebar;
7224
- exports.Skeleton = Skeleton;
7350
+ exports.SkeletonBlock = SkeletonBlock;
7351
+ exports.SkeletonLabel = SkeletonLabel;
7225
7352
  exports.SkeletonLines = SkeletonLines;
7226
7353
  exports.Spacer = Spacer;
7227
7354
  exports.Spinner = Spinner;