Coverage for src / lexigram / ui / atoms / inputs / numeric.py: 40%

30 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 

4 

5from lexigram.ui.atoms.inputs.base import AbstractInput 

6from lexigram.ui.core.base import el 

7 

8 

9class NumberInput(AbstractInput): 

10 """ 

11 Number input with min/max/step validation. 

12 

13 Args: 

14 name: Input name attribute 

15 value: Input value 

16 min: Minimum allowed value 

17 max: Maximum allowed value 

18 step: Step increment 

19 label: Optional label text 

20 error: Error message to display 

21 **kwargs: Additional props 

22 """ 

23 

24 def __init__( 

25 self, 

26 name: str, 

27 value: float | None = None, 

28 min_value: float | None = None, 

29 max_value: float | None = None, 

30 step: float | None = None, 

31 placeholder: str | None = None, 

32 **kwargs, 

33 ): 

34 super().__init__(name=name, value=value, **kwargs) 

35 self.min = min_value 

36 self.max = max_value 

37 self.step = step 

38 self.placeholder = placeholder 

39 

40 def _render_input(self) -> Any: 

41 return el( 

42 "input", 

43 type="number", 

44 name=self.name, 

45 id=self.input_id, 

46 value=str(self.value) if self.value is not None else "", 

47 min=self.min, 

48 max=self.max, 

49 step=self.step, 

50 placeholder=self.placeholder, 

51 disabled=self.disabled, 

52 required=self.required, 

53 readonly=self.readonly, 

54 class_=self._get_input_classes(), 

55 **self._get_extra_props(["min", "max", "step", "placeholder"]), 

56 ) 

57 

58 

59class Slider(AbstractInput): 

60 """ 

61 Range slider input. 

62 

63 Args: 

64 name: Input name attribute 

65 value: Current value 

66 min: Minimum value (default: 0) 

67 max: Maximum value (default: 100) 

68 step: Step increment (default: 1) 

69 label: Optional label text 

70 **kwargs: Additional props 

71 """ 

72 

73 SLIDER_CLASSES = ( 

74 "w-full h-2 bg-muted rounded-lg appearance-none " 

75 "cursor-pointer accent-[var(--primary)]" 

76 ) 

77 

78 def __init__( 

79 self, 

80 name: str, 

81 value: float = 0, 

82 min_value: float = 0, 

83 max_value: float = 100, 

84 step: float = 1, 

85 **kwargs, 

86 ): 

87 super().__init__(name=name, value=value, **kwargs) 

88 self.min = min_value 

89 self.max = max_value 

90 self.step = step 

91 

92 def _render_input(self) -> Any: 

93 return el( 

94 "input", 

95 type="range", 

96 name=self.name, 

97 id=self.input_id, 

98 value=self.value, 

99 min=self.min, 

100 max=self.max, 

101 step=self.step, 

102 disabled=self.disabled, 

103 class_=self.SLIDER_CLASSES, 

104 ) 

105 

106 def render(self) -> Any: 

107 """Custom render with value display.""" 

108 slider = self._render_input() 

109 

110 range_display = el( 

111 "div", 

112 el("span", str(self.min), class_="text-xs text-muted-foreground"), 

113 el("span", str(self.max), class_="text-xs text-muted-foreground"), 

114 class_="flex justify-between mt-1", 

115 ) 

116 

117 content = el("div", slider, range_display) 

118 

119 if self.label: 

120 header = el( 

121 "div", 

122 el( 

123 "label", 

124 self.label, 

125 class_="block text-sm font-medium text-foreground", 

126 ), 

127 el( 

128 "span", 

129 str(self.value), 

130 class_="text-sm font-semibold text-primary", 

131 ), 

132 class_="flex justify-between mb-2", 

133 ) 

134 return el("div", header, content, class_="mb-6") 

135 

136 return content