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
« prev ^ index » next coverage.py v7.13.5, created at 2026-08-10 04:11 +0800
1from __future__ import annotations
3from typing import Any, Literal
5from lexigram.ui.core.base import Component, el
8class Spinner(Component):
9 """Circular loading spinner with size variants.
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 """
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
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 }
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 )