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

1"""Top-row stat cards — label, big value, optional colored delta line.""" 

2 

3from __future__ import annotations 

4 

5from textual.app import ComposeResult 

6from textual.containers import Vertical 

7from textual.widgets import Static 

8 

9from secchi.ui import palette 

10 

11 

12class StatCard(Vertical): 

13 """A compact stat card: label on top, value below, optional delta subline. 

14 

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 """ 

19 

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") 

40 

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") 

46 

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()) 

67 

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}[/]" 

73 

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 "" 

79 

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 ""