Coverage for src / lexigram / ui / molecules / section.py: 100%

30 statements  

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

1""" 

2Section component for grouping form fields. 

3 

4Provides titled sections with optional descriptions and collapsible functionality. 

5""" 

6 

7from __future__ import annotations 

8 

9from typing import Any 

10 

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

12 

13 

14class Section(Component): 

15 """ 

16 Form section component for grouping related fields. 

17 

18 Example: 

19 Section( 

20 title="Personal Information", 

21 description="Basic details about the user", 

22 Grid( 

23 TextInput("first_name", label="First Name"), 

24 TextInput("last_name", label="Last Name"), 

25 cols=2 

26 ) 

27 ) 

28 """ 

29 

30 def __init__( 

31 self, 

32 *children, 

33 title: str, 

34 description: str | None = None, 

35 icon: str | None = None, 

36 collapsible: bool = False, 

37 collapsed: bool = False, 

38 **props, 

39 ): 

40 """ 

41 Initialize section component. 

42 

43 Args: 

44 *children: Child components 

45 title: Section title 

46 description: Optional description 

47 icon: Optional icon (emoji or icon class) 

48 collapsible: Whether section can be collapsed 

49 collapsed: Initial collapsed state 

50 **props: Additional properties 

51 """ 

52 super().__init__( 

53 *children, 

54 title=title, 

55 description=description, 

56 icon=icon, 

57 collapsible=collapsible, 

58 collapsed=collapsed, 

59 **props, 

60 ) 

61 self.children = children # type: ignore[assignment] 

62 self.title = title 

63 self.description = description 

64 self.icon = icon 

65 self.collapsible = collapsible 

66 self.collapsed = collapsed 

67 

68 def render(self) -> Any: 

69 """Render the section.""" 

70 from lexigram.ui.core.base import raw, render_to_string 

71 

72 # Render children 

73 children_html = [ 

74 raw(render_to_string(c)) 

75 if hasattr(c, "__html__") or hasattr(c, "render") 

76 else str(c) 

77 for c in self.children 

78 ] 

79 

80 # Icon element 

81 icon_el = "" 

82 if self.icon: 

83 icon_el = el("span", self.icon, class_="text-xl mr-2") 

84 

85 # Collapse button 

86 collapse_btn = "" 

87 if self.collapsible: 

88 collapse_btn = el( 

89 "button", 

90 type="button", 

91 class_="ml-2 text-muted-foreground hover:text-foreground", 

92 aria_expanded=str(not self.collapsed).lower(), 

93 x_on_click="collapsed = !collapsed", 

94 x_text="collapsed ? '▶' : '▼'", 

95 ) 

96 

97 # Title section 

98 title_section = el( 

99 "div", 

100 el( 

101 "h3", 

102 icon_el, 

103 self.title, 

104 collapse_btn, 

105 class_="text-lg font-semibold text-foreground flex items-center", 

106 ), 

107 el( 

108 "p", 

109 self.description, 

110 class_="mt-1 text-sm text-muted-foreground", 

111 ) 

112 if self.description 

113 else "", 

114 class_="mb-4", 

115 ) 

116 

117 # Content section 

118 content_attrs: dict[str, Any] = {} 

119 if self.collapsible: 

120 content_attrs["x_show"] = "!collapsed" 

121 

122 content_section = el( 

123 "div", 

124 *children_html, 

125 id=f"{self.title.replace(' ', '_')}_content", 

126 class_="space-y-4", 

127 **content_attrs, 

128 ) 

129 

130 section_attrs = {} 

131 if self.collapsible: 

132 section_attrs["x_data"] = f"{{ collapsed: {'true' if self.collapsed else 'false'} }}" 

133 

134 return el( 

135 "div", 

136 title_section, 

137 content_section, 

138 class_="bg-card rounded-lg border border-border p-6 mb-6", 

139 **section_attrs, 

140 )