Coverage for src/secchi/ui/widgets/stat_card.py: 26%
46 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-08-04 22:15 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-08-04 22:15 +0000
1"""Top-row stat cards — label, big value, optional colored delta line."""
3from __future__ import annotations
5from textual.app import ComposeResult
6from textual.containers import Vertical
7from textual.widgets import Static
9from secchi.ui import palette
12class StatCard(Vertical):
13 """A compact stat card: label on top, value below, optional delta subline.
15 Delta color convention is metric-specific and set by the caller via
16 `delta_good` (True → positive delta is green; False → negative is green,
17 e.g. for open-issue counts where fewer is healthier).
18 """
20 def __init__(
21 self,
22 label: str,
23 value: str = "—",
24 *,
25 delta: str = "",
26 signal: str = "",
27 value_color: str = "",
28 delta_color: str = "",
29 signal_color: str = "",
30 ) -> None:
31 self._label = label
32 self._value = value
33 self._delta = delta
34 self._signal = signal
35 self._value_color = value_color
36 self._delta_color = delta_color
37 self._signal_color = signal_color
38 super().__init__()
39 self.add_class("stat-card")
41 def compose(self) -> ComposeResult:
42 yield Static(self._label, classes="stat-card-label")
43 yield Static(self._value_markup(), classes="stat-card-value")
44 yield Static(self._delta_markup(), classes="stat-card-delta")
45 yield Static(self._signal_markup(), classes="stat-card-signal")
47 def set(
48 self,
49 value: str,
50 *,
51 delta: str = "",
52 signal: str = "",
53 value_color: str = "",
54 delta_color: str = "",
55 signal_color: str = "",
56 ) -> None:
57 self._value = value
58 self._delta = delta
59 self._signal = signal
60 self._value_color = value_color
61 self._delta_color = delta_color
62 self._signal_color = signal_color
63 if self.is_mounted:
64 self.query_one(".stat-card-value", Static).update(self._value_markup())
65 self.query_one(".stat-card-delta", Static).update(self._delta_markup())
66 self.query_one(".stat-card-signal", Static).update(self._signal_markup())
68 def _value_markup(self) -> str:
69 if self._value_color:
70 return f"[b {self._value_color}]{self._value}[/]"
71 else:
72 return f"[b {palette.GREEN}]{self._value}[/]"
74 def _delta_markup(self) -> str:
75 if self._delta:
76 color = self._delta_color or "dim"
77 return f"[{color}]{self._delta}[/]"
78 return ""
80 def _signal_markup(self) -> str:
81 if self._signal:
82 color = self._signal_color or "dim"
83 return f"[{color}]{self._signal}[/]"
84 return ""