aura-glass
Version:
A comprehensive glassmorphism design system for React applications with 142+ production-ready components
335 lines (332 loc) • 12.3 kB
JavaScript
'use client';
import { jsx, jsxs } from 'react/jsx-runtime';
import { cn } from '../../lib/utilsComprehensive.js';
import { ArrowUpIcon, ArrowDownIcon, Minus, BarChart3, Zap, Target, TrendingUp, Activity, Users, DollarSign } from 'lucide-react';
import '../../primitives/GlassCore.js';
import '../../primitives/glass/GlassAdvanced.js';
import '../../primitives/OptimizedGlassCore.js';
import '../../primitives/glass/OptimizedGlassAdvanced.js';
import '../../primitives/MotionNative.js';
import { MotionFramer } from '../../primitives/motion/MotionFramer.js';
import { CardHeader, CardTitle, CardContent } from '../card/index.js';
import { GlassCard } from '../card/GlassCard.js';
/**
* GlassStatCard component
* A glassmorphism statistics card with trend indicators and additional metrics
*/
const GlassStatCard = ({
// TODO: Integrate ContrastGuard for table cells, list items, badges, card titles, and other text content for WCAG AA compliance
title,
value,
unit = "",
description,
icon,
type = "custom",
variant = "default",
size = "md",
layout = "vertical",
trend,
previousValue,
additionalStats,
showSparkline = false,
sparklineData = [],
progress,
loading = false,
onClick,
className,
...props
}) => {
// Size configurations
const sizeConfigs = {
sm: {
cardClass: "glass-p-4",
titleClass: "glass-text-sm font-medium",
valueClass: "glass-text-xl font-bold",
iconSize: "w-6 h-6",
sparklineHeight: "h-6"
},
md: {
cardClass: "glass-p-6",
titleClass: "glass-text-base font-medium",
valueClass: "glass-text-2xl font-bold",
iconSize: "w-8 h-8",
sparklineHeight: "h-8"
},
lg: {
cardClass: "p-8",
titleClass: "glass-text-lg font-semibold",
valueClass: "text-3xl font-bold",
iconSize: "w-10 h-10",
sparklineHeight: "h-10"
},
xl: {
cardClass: "glass-p-10",
titleClass: "glass-text-xl font-semibold",
valueClass: "text-4xl font-bold",
iconSize: "w-12 h-12",
sparklineHeight: "h-12"
}
};
// Get automatic icon based on type
const getTypeIcon = () => {
switch (type) {
case "revenue":
return jsx(DollarSign, {
className: sizeConfigs[size].iconSize
});
case "users":
return jsx(Users, {
className: sizeConfigs[size].iconSize
});
case "conversion":
return jsx(Target, {
className: sizeConfigs[size].iconSize
});
case "performance":
return jsx(Activity, {
className: sizeConfigs[size].iconSize
});
case "growth":
return jsx(TrendingUp, {
className: sizeConfigs[size].iconSize
});
case "target":
return jsx(Target, {
className: sizeConfigs[size].iconSize
});
case "activity":
return jsx(Zap, {
className: sizeConfigs[size].iconSize
});
case "analytics":
return jsx(BarChart3, {
className: sizeConfigs[size].iconSize
});
default:
return null;
}
};
// Variant color configurations
const variantConfigs = {
default: {
iconColor: "glass-text-primary/70",
valueColor: "glass-text-primary",
trendUpColor: "text-green-400",
trendDownColor: "text-red-400",
trendNeutralColor: "text-yellow-400",
sparklineColor: "glass-text-primary/60"
},
success: {
iconColor: "text-green-400",
valueColor: "text-green-200",
trendUpColor: "text-green-300",
trendDownColor: "text-red-400",
trendNeutralColor: "text-yellow-400",
sparklineColor: "text-green-400"
},
warning: {
iconColor: "text-yellow-400",
valueColor: "text-yellow-200",
trendUpColor: "text-green-400",
trendDownColor: "text-red-400",
trendNeutralColor: "text-yellow-300",
sparklineColor: "text-yellow-400"
},
error: {
iconColor: "text-red-400",
valueColor: "text-red-200",
trendUpColor: "text-red-400",
trendDownColor: "text-red-300",
trendNeutralColor: "text-yellow-400",
sparklineColor: "text-red-400"
},
info: {
iconColor: "text-blue-400",
valueColor: "text-blue-200",
trendUpColor: "text-green-400",
trendDownColor: "text-red-400",
trendNeutralColor: "text-yellow-400",
sparklineColor: "text-blue-400"
},
primary: {
iconColor: "text-primary",
valueColor: "text-primary-foreground",
trendUpColor: "text-green-400",
trendDownColor: "text-red-400",
trendNeutralColor: "text-yellow-400",
sparklineColor: "text-primary"
}
};
const config = sizeConfigs?.[size];
const variantConfig = variantConfigs?.[variant];
const displayIcon = icon || getTypeIcon();
// Generate sparkline path
const generateSparklinePath = data => {
if ((data?.length || 0) === 0) return "";
const min = Math.min(...data);
const max = Math.max(...data);
const range = max - min || 1;
const height = parseInt(config.sparklineHeight.replace("h-", "")) * 4; // Convert to pixels
let path = "";
data?.forEach((value, index) => {
const x = index / ((data?.length || 0) - 1) * 100;
const y = height - (value - min) / range * height;
if (index === 0) {
path += `M ${x} ${y}`;
} else {
path += ` L ${x} ${y}`;
}
});
return path;
};
// Loading skeleton
if (loading) {
return jsx(GlassCard, {
"data-glass-component": true,
className: cn("animate-pulse", config.cardClass, className),
children: jsxs("div", {
className: "glass-gap-4",
children: [jsxs("div", {
className: "glass-flex glass-items-center glass-justify-between",
children: [jsx("div", {
className: 'h-4 glass-surface-subtle/20 glass-radius-md w-24'
}), jsx("div", {
className: 'w-8 h-8 glass-surface-subtle/20 glass-radius-md'
})]
}), jsxs("div", {
className: "glass-gap-2",
children: [jsx("div", {
className: 'h-8 glass-surface-subtle/20 glass-radius-md w-32'
}), jsx("div", {
className: 'h-4 glass-surface-subtle/20 glass-radius-md w-20'
})]
}), showSparkline && jsx("div", {
className: cn("bg-white/10 glass-radius-md", config.sparklineHeight)
})]
})
});
}
return jsx(MotionFramer, {
preset: "fadeIn",
className: "glass-w-full glass-stat-card",
children: jsxs(GlassCard, {
variant: variant === "default" ? "default" : "elevated",
elevation: "level2",
hoverable: !!onClick,
clickable: !!onClick,
onClick: onClick,
className: cn(config.cardClass, "group relative overflow-hidden", onClick && ["cursor-pointer", "hover:shadow-2xl hover:shadow-emerald-500/20", "transition-all duration-500 ease-out"], className),
...props,
children: [jsx(CardHeader, {
className: 'pb-2',
children: jsxs("div", {
className: cn("flex items-start justify-between", layout === "horizontal" ? "flex-row items-center" : "flex-col"),
children: [jsxs("div", {
className: "glass-flex-1",
children: [jsxs(CardTitle, {
className: cn(config.titleClass, "glass-text-primary/90 flex items-center glass-gap-2"),
children: [displayIcon && jsx("span", {
className: cn(config.iconSize, variantConfig.iconColor),
children: displayIcon
}), title]
}), description && jsx("p", {
className: 'glass-text-sm text-primary/60 glass-mt-1',
children: description
})]
}), trend && jsxs("div", {
className: "glass-flex glass-items-center glass-gap-1 glass-mt-2",
children: [trend.direction === "up" && jsx(ArrowUpIcon, {
className: cn("w-4 h-4", variantConfig.trendUpColor)
}), trend.direction === "down" && jsx(ArrowDownIcon, {
className: cn("w-4 h-4", variantConfig.trendDownColor)
}), trend.direction === "neutral" && jsx(Minus, {
className: cn("w-4 h-4", variantConfig.trendNeutralColor)
}), jsxs("span", {
className: cn("glass-text-sm font-medium", trend.direction === "up" ? variantConfig.trendUpColor : trend.direction === "down" ? variantConfig.trendDownColor : variantConfig.trendNeutralColor),
children: [trend.value > 0 ? "+" : "", trend.value, "% ", trend.label]
})]
})]
})
}), jsxs(CardContent, {
className: 'pt-0',
children: [jsxs("div", {
className: cn("flex items-baseline glass-gap-2 glass-mb-4", layout === "horizontal" ? "justify-start" : "justify-center"),
children: [jsx("span", {
className: cn(config.valueClass, variantConfig.valueColor),
children: value
}), unit && jsx("span", {
className: 'glass-text-lg text-primary/70 font-medium',
children: unit
})]
}), showSparkline && (sparklineData?.length || 0) > 0 && jsx("div", {
className: 'mb-4',
children: jsx("svg", {
width: "100%",
height: config.sparklineHeight.replace("h-", ""),
className: 'overflow-visible',
children: jsx("path", {
d: generateSparklinePath(sparklineData),
fill: "none",
stroke: "currentColor",
strokeWidth: "2",
className: cn("opacity-60", variantConfig.sparklineColor)
})
})
}), progress !== undefined && jsxs("div", {
className: 'mb-4',
children: [jsx("div", {
className: 'glass-w-full glass-surface-subtle/10 glass-radius-full h-2 overflow-hidden',
children: jsx(MotionFramer, {
preset: "slideRight",
className: "glass-h-full glass-gradient-primary glass-gradient-primary glass-gradient-primary glass-radius-full",
style: {
width: `${Math.min(progress, 100)}%`
}
})
}), jsxs("div", {
className: "glass-flex glass-justify-between glass-items-center glass-mt-1",
children: [jsx("span", {
className: 'glass-text-xs text-primary/60',
children: "Progress"
}), jsxs("span", {
className: 'glass-text-xs text-primary/60',
children: [Math.round(progress), "%"]
})]
})]
}), previousValue && jsx("div", {
className: 'mb-4 glass-p-3 glass-surface-subtle/5 glass-radius-lg',
children: jsxs("div", {
className: "glass-flex glass-items-center glass-justify-between",
children: [jsx("span", {
className: 'glass-text-sm text-primary/70',
children: "Previous period"
}), jsxs("span", {
className: 'glass-text-sm font-medium text-primary',
children: [previousValue, unit]
})]
})
}), additionalStats && (additionalStats?.length || 0) > 0 && jsx("div", {
className: "glass-gap-3",
children: additionalStats.map((stat, index) => jsxs("div", {
className: "glass-flex glass-items-center glass-justify-between",
children: [jsx("span", {
className: 'glass-text-sm text-primary/70',
children: stat.label
}), jsxs("div", {
className: "glass-flex glass-items-center glass-gap-2",
children: [jsx("span", {
className: 'glass-text-sm font-medium text-primary',
children: stat.value
}), stat.change !== undefined && jsxs("span", {
className: cn("glass-text-xs font-medium", stat.change > 0 ? "text-green-400" : stat.change < 0 ? "text-red-400" : "text-yellow-400"),
children: [stat.change > 0 ? "+" : "", stat.change, "%"]
})]
})]
}, index))
})]
})]
})
});
};
export { GlassStatCard, GlassStatCard as default };
//# sourceMappingURL=GlassStatCard.js.map