Coverage for src / lexigram / ui / molecules / metric_card.py: 26%
31 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-08-10 04:11 +0800
« prev ^ index » next coverage.py v7.13.5, created at 2026-08-10 04:11 +0800
1"""
2MetricCard component for displaying key metrics.
4Shows a number with label, optional trend indicator, and icon.
5Perfect for dashboard statistics.
6"""
8from __future__ import annotations
10from typing import Any, Literal
12from lexigram.ui.core.base import Component, el
14MetricCardVariant = Literal["default", "success", "warning", "danger", "info"]
15TrendDirection = Literal["up", "down"]
18class MetricCard(Component):
19 """
20 MetricProtocol card for dashboard statistics.
22 Example:
23 MetricCard(
24 value="1,234",
25 label="Total Users",
26 trend="+12%",
27 trend_direction="up",
28 icon="👥",
29 color="success"
30 )
31 """
33 def __init__(
34 self,
35 value: str | float,
36 label: str,
37 trend: str | None = None,
38 trend_direction: TrendDirection | None = None,
39 icon: str | None = None,
40 variant: MetricCardVariant = "default",
41 **props,
42 ):
43 """
44 Initialize metric card.
46 Args:
47 value: The metric value (number or formatted string)
48 label: Description label
49 trend: Trend indicator (e.g., "+12%", "-5%")
50 trend_direction: Direction of trend ("up" or "down")
51 icon: Optional icon (emoji or icon class)
52 variant: Color variant
53 **props: Additional properties
54 """
55 super().__init__(
56 value=value,
57 label=label,
58 trend=trend,
59 trend_direction=trend_direction,
60 icon=icon,
61 color=variant,
62 **props,
63 )
64 self.value = value
65 self.label = label
66 self.trend = trend
67 self.trend_direction = trend_direction
68 self.icon = icon
69 self.variant = variant
71 def render(self) -> Any:
72 """Render the metric card."""
73 # Variant color classes
74 variant_classes = {
75 "default": "border-border",
76 "success": "border-success/30 bg-success/10",
77 "warning": "border-warning/30 bg-warning/10",
78 "danger": "border-destructive/30 bg-destructive/10",
79 "info": "border-info/30 bg-info/10",
80 }
82 variant_text_classes = {
83 "default": "text-foreground",
84 "success": "text-success",
85 "warning": "text-warning",
86 "danger": "text-destructive",
87 "info": "text-info",
88 }
90 card_classes = f"bg-card rounded-lg border-2 {variant_classes.get(self.variant, variant_classes['default'])} p-6 shadow-sm hover:shadow-md transition-shadow"
91 value_classes = f"text-3xl font-bold {variant_text_classes.get(self.variant, variant_text_classes['default'])}"
93 # Icon element
94 icon_el = ""
95 if self.icon:
96 icon_el = el("div", self.icon, class_="text-3xl mb-2 opacity-50")
98 # Trend element
99 trend_el = ""
100 if self.trend:
101 trend_color = "text-success"
102 trend_arrow = "↑"
104 if self.trend_direction == "down":
105 trend_color = "text-destructive"
106 trend_arrow = "↓"
108 trend_el = el(
109 "div",
110 el("span", trend_arrow, class_="font-bold"),
111 " ",
112 el("span", self.trend),
113 class_=f"text-sm font-medium {trend_color} mt-1",
114 )
116 return el(
117 "div",
118 icon_el,
119 el("div", str(self.value), class_=value_classes),
120 el(
121 "div",
122 self.label,
123 class_="text-sm font-medium text-muted-foreground mt-1",
124 ),
125 trend_el,
126 class_=card_classes,
127 )