Coverage for src / lexigram / ui / config.py: 100%

102 statements  

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

1"""UIConfig — configuration model for the UI provider.""" 

2 

3from __future__ import annotations 

4 

5from dataclasses import dataclass, field 

6from typing import Any, ClassVar 

7 

8from lexigram.config import BaseConfig 

9from lexigram.contracts.core.config import ConfigIssue, Environment 

10from lexigram.ui.constants import ENV_NESTED_DELIMITER, ENV_PREFIX 

11from lexigram.validation import ConfigDict, Field 

12 

13 

14@dataclass 

15class DebounceConfig: 

16 """Configuration for debounced HTMX triggers.""" 

17 

18 delay_ms: int = 300 

19 changed: bool = True 

20 

21 def to_trigger(self, base_trigger: str = "input") -> str: 

22 """Generate HTMX trigger string with debounce.""" 

23 parts = [base_trigger] 

24 if self.changed: 

25 parts.append("changed") 

26 parts.append(f"delay:{self.delay_ms}ms") 

27 return " ".join(parts) 

28 

29 

30@dataclass(init=False) 

31class UIConfig(BaseConfig): 

32 """Configuration for the lexigram-ui provider. 

33 

34 These settings control rendering defaults and are read from the 

35 ``[ui]`` section of ``application.yaml`` (or equivalent config source). 

36 """ 

37 

38 config_section: ClassVar[str] = "ui" 

39 

40 model_config: ClassVar[ConfigDict] = ConfigDict( 

41 env_prefix=ENV_PREFIX, # type: ignore[typeddict-unknown-key] 

42 env_nested_delimiter=ENV_NESTED_DELIMITER, 

43 extra="ignore", 

44 ) 

45 

46 default_theme: str = Field(default="default", description="Default CSS theme name.") 

47 auto_escape: bool = Field( 

48 default=True, description="HTML-escape user strings by default." 

49 ) 

50 htmx_version: str = Field(default="2.0.4", description="HTMX CDN version.") 

51 debug_components: bool = Field( 

52 default=False, description="Render data-component debug attributes." 

53 ) 

54 theme: str = Field(default="light", description="Active UI theme.") 

55 enable_sse: bool = Field( 

56 default=False, description="Enable Server-Sent Events support." 

57 ) 

58 enable_realtime: bool = Field( 

59 default=False, description="Enable realtime update features." 

60 ) 

61 

62 def validate_for_environment( 

63 self, env: Environment | None = None 

64 ) -> list[ConfigIssue]: 

65 """Check config is safe for the target environment.""" 

66 issues: list[ConfigIssue] = [] 

67 if env == Environment.PRODUCTION: 

68 if self.debug_components: 

69 issues.append( 

70 ConfigIssue( 

71 severity="warning", 

72 field="debug_components", 

73 message="Component debugging enabled in production", 

74 suggestion="Set debug_components: false for performance", 

75 ) 

76 ) 

77 return issues 

78 

79 

80@dataclass 

81class HTMLDocumentConfig: 

82 """Configuration for HTML document generation.""" 

83 

84 # Document basics 

85 lang: str = "en" 

86 charset: str = "UTF-8" 

87 

88 # Meta tags 

89 viewport: str = "width=device-width, initial-scale=1.0" 

90 description: str = "" 

91 keywords: list[str] = field(default_factory=list) 

92 author: str = "" 

93 robots: str = "" # e.g., "noindex, nofollow" 

94 

95 # Favicon 

96 favicon: str | None = None 

97 favicon_type: str = "image/x-icon" 

98 

99 # Theme 

100 theme_color: str = "#ffffff" 

101 

102 # Open Graph 

103 og_title: str = "" 

104 og_description: str = "" 

105 og_image: str = "" 

106 og_url: str = "" 

107 og_type: str = "website" 

108 

109 # Additional head content 

110 extra_head: str = "" 

111 

112 

113@dataclass 

114class BaseLayoutConfig(HTMLDocumentConfig): 

115 """Base configuration for all layouts. 

116 

117 Extends HTMLDocumentConfig with common layout settings. 

118 """ 

119 

120 # Branding 

121 site_name: str = "Lexigram Admin" 

122 site_logo: str | None = None 

123 site_logo_alt: str = "Logo" 

124 

125 # Theme 

126 theme: str = "light" 

127 primary_color: str = "#6b7280" 

128 accent_color: str = "#8b5cf6" 

129 

130 # HTMX 

131 htmx_enabled: bool = True 

132 htmx_boost: bool = True 

133 htmx_version: str = "1.9.10" 

134 

135 # External resources 

136 css_files: list[str] = field(default_factory=list) 

137 js_files: list[str] = field(default_factory=list) 

138 

139 # Features 

140 include_alpine: bool = False 

141 alpine_version: str = "3.x.x" 

142 

143 

144@dataclass 

145class HeadConfig: 

146 """Configuration for head section.""" 

147 

148 # Default CSS framework 

149 css_framework: str = "tailwind" # tailwind, pico, custom 

150 css_framework_url: str = "" 

151 

152 # Custom CSS 

153 css_files: list[str] = field(default_factory=list) 

154 inline_css: str = "" 

155 

156 # Icons 

157 icon_library: str = "lucide" # lucide, heroicons, feather 

158 icon_library_url: str = "https://unpkg.com/lucide@0.263.1/dist/umd/lucide.min.js" 

159 

160 # Fonts 

161 font_url: str = "" 

162 

163 # HTMX 

164 htmx_url: str = "https://unpkg.com/htmx.org@1.9.10" 

165 include_hyperscript: bool = False 

166 hyperscript_url: str = "https://unpkg.com/hyperscript.org@0.9.12" 

167 

168 

169@dataclass 

170class FooterConfig: 

171 """Configuration for footer.""" 

172 

173 # Copyright 

174 show_copyright: bool = True 

175 copyright_holder: str = "" 

176 copyright_start_year: int | None = None 

177 

178 # Version 

179 show_version: bool = True 

180 version: str = "" 

181 

182 # Links 

183 links: list[Any] = field(default_factory=list) 

184 

185 # Styling 

186 sticky: bool = False 

187 show_divider: bool = True 

188 

189 # Custom content 

190 custom_left: str = "" 

191 custom_right: str = "" 

192 

193 

194@dataclass 

195class ToastConfig: 

196 """Configuration for toast container.""" 

197 

198 # Position 

199 position: str = "top-right" # top-right, top-left, bottom-right, bottom-left 

200 

201 # Behavior 

202 default_duration_ms: int = 5000 

203 max_toasts: int = 5 

204 stack_direction: str = "down" # up or down 

205 

206 # HTMX 

207 listen_for_events: bool = True 

208 event_name: str = "showToast" 

209 

210 # Animation 

211 animation_in: str = "fade-in-right" 

212 animation_out: str = "fade-out-right" 

213 

214 

215__all__ = [ 

216 "BaseLayoutConfig", 

217 "DebounceConfig", 

218 "FooterConfig", 

219 "HTMLDocumentConfig", 

220 "HeadConfig", 

221 "ToastConfig", 

222 "UIConfig", 

223]