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

11 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 Spinner(Component): 

9 """Circular loading spinner with size variants. 

10 

11 Args: 

12 size: Size variant (sm=16px, md=24px, lg=32px, xl=48px) 

13 aria_label: Accessible label announced to screen readers (default: "Loading...") 

14 """ 

15 

16 def __init__( 

17 self, 

18 size: Literal["sm", "md", "lg", "xl"] = "md", 

19 aria_label: str = "Loading...", 

20 **props, 

21 ) -> None: 

22 super().__init__(size=size, **props) 

23 self.size = size 

24 self.aria_label = aria_label 

25 

26 def render(self) -> Any: 

27 size_map = { 

28 "sm": "w-4 h-4", 

29 "md": "w-6 h-6", 

30 "lg": "w-8 h-8", 

31 "xl": "w-12 h-12", 

32 } 

33 

34 return el( 

35 "svg", 

36 el( 

37 "circle", 

38 cx="12", 

39 cy="12", 

40 r="10", 

41 stroke="currentColor", 

42 stroke_width="4", 

43 fill="none", 

44 class_="opacity-25", 

45 ), 

46 el( 

47 "path", 

48 d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z", 

49 fill="currentColor", 

50 class_="opacity-75", 

51 ), 

52 role="status", 

53 aria_live="polite", 

54 aria_label=self.aria_label, 

55 class_=f"{size_map[self.size]} text-primary animate-spin opacity-70", 

56 xmlns="http://www.w3.org/2000/svg", 

57 viewBox="0 0 24 24", 

58 )