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
« prev ^ index » next coverage.py v7.13.5, created at 2026-08-10 04:11 +0800
1"""
2Section component for grouping form fields.
4Provides titled sections with optional descriptions and collapsible functionality.
5"""
7from __future__ import annotations
9from typing import Any
11from lexigram.ui.core.base import Component, el
14class Section(Component):
15 """
16 Form section component for grouping related fields.
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 """
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.
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
68 def render(self) -> Any:
69 """Render the section."""
70 from lexigram.ui.core.base import raw, render_to_string
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 ]
80 # Icon element
81 icon_el = ""
82 if self.icon:
83 icon_el = el("span", self.icon, class_="text-xl mr-2")
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 )
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 )
117 # Content section
118 content_attrs: dict[str, Any] = {}
119 if self.collapsible:
120 content_attrs["x_show"] = "!collapsed"
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 )
130 section_attrs = {}
131 if self.collapsible:
132 section_attrs["x_data"] = f"{{ collapsed: {'true' if self.collapsed else 'false'} }}"
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 )