Coverage for src / lexigram / ui / atoms / progress_bar.py: 100%

18 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-08-10 04:11 +0800

1from __future__ import annotations 

2 

3from typing import Any, Literal 

4 

5from lexigram.ui.core.base import Component, el 

6 

7 

8class ProgressBar(Component): 

9 """Linear progress bar with percentage display. 

10 

11 Args: 

12 value: Current progress value 

13 max: Maximum value (default 100) 

14 label: Optional label text 

15 show_percentage: Whether to show percentage text 

16 size: Height variant (sm, md, lg) 

17 """ 

18 

19 def __init__( 

20 self, 

21 value: int, 

22 max_value: int = 100, 

23 label: str | None = None, 

24 show_percentage: bool = True, 

25 size: Literal["sm", "md", "lg"] = "md", 

26 **props, 

27 ) -> None: 

28 super().__init__( 

29 value=value, 

30 max=max_value, 

31 label=label, 

32 show_percentage=show_percentage, 

33 size=size, 

34 **props, 

35 ) 

36 self.value = value 

37 self.max = max_value 

38 self.label = label 

39 self.show_percentage = show_percentage 

40 self.size = size 

41 

42 def render(self) -> Any: 

43 percentage = int((self.value / self.max) * 100) if self.max > 0 else 0 

44 

45 size_map = {"sm": "h-1", "md": "h-2", "lg": "h-3"} 

46 

47 header = "" 

48 if self.label or self.show_percentage: 

49 header = el( 

50 "div", 

51 el( 

52 "span", 

53 self.label or "", 

54 class_="text-sm font-medium text-foreground opacity-70", 

55 ) 

56 if self.label 

57 else "", 

58 el( 

59 "span", 

60 f"{percentage}%", 

61 class_="text-sm font-medium text-foreground opacity-70", 

62 ) 

63 if self.show_percentage 

64 else "", 

65 class_="flex justify-between mb-1", 

66 ) 

67 

68 return el( 

69 "div", 

70 header, 

71 el( 

72 "div", 

73 el( 

74 "div", 

75 role="progressbar", 

76 aria_valuenow=str(self.value), 

77 aria_valuemin="0", 

78 aria_valuemax=str(self.max), 

79 aria_label=self.label or f"{percentage}%", 

80 class_=f"bg-primary {size_map[self.size]} rounded-full transition-all duration-300", 

81 style=f"width: {percentage}%", 

82 ), 

83 class_=f"w-full bg-muted rounded-full {size_map[self.size]} overflow-hidden", 

84 ), 

85 class_="w-full", 

86 )