Coverage for src / lexigram / ui / atoms / layout.py: 100%
109 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
5from lexigram.ui.core.base import Component, el
8class Row(Component):
9 """Grid row component (flexible layout)."""
11 def __init__(self, *children, cols: int = 1, gap: int = 4, **props) -> None:
12 super().__init__(cols=cols, gap=gap, **props)
13 if children:
14 self.children = list(children)
15 self.cols = cols
16 self.gap = gap
18 def render(self) -> Any:
19 from lexigram.ui.core.base import raw, render_to_string
21 children_html = [
22 raw(render_to_string(c))
23 if hasattr(c, "__html__") or hasattr(c, "render")
24 else str(c)
25 for c in self.children
26 ]
27 # Merge layout classes with any custom classes in props
28 cls = f"grid grid-cols-{self.cols} gap-{self.gap}"
29 custom_cls = self.props.get("class_", self.props.get("class"))
30 if custom_cls:
31 cls = f"{cls} {custom_cls}"
33 # Create a copy of props to avoid mutating state, and remove layout-specifics
34 attrs = {
35 k: v
36 for k, v in self.props.items()
37 if k not in ("cols", "gap", "class_", "class")
38 }
39 return el("div", *children_html, class_=cls, **attrs)
42class Col(Component):
43 """Grid column component (vertical flow)."""
45 def __init__(
46 self,
47 *children,
48 gap: int = 4,
49 span: int | None = None,
50 **props,
51 ) -> None:
52 super().__init__(gap=gap, span=span, **props)
53 if children:
54 self.children = list(children)
55 self.gap = gap
56 self.span = span
58 def render(self) -> Any:
59 from lexigram.ui.core.base import raw, render_to_string
61 children_html = [
62 raw(render_to_string(c))
63 if hasattr(c, "__html__") or hasattr(c, "render")
64 else str(c)
65 for c in self.children
66 ]
67 cls = f"flex flex-col gap-{self.gap}"
69 # Add span class if specified
70 if self.span:
71 cls = f"{cls} col-span-{self.span}"
73 custom_cls = self.props.get("class_", self.props.get("class"))
74 if custom_cls:
75 cls = f"{cls} {custom_cls}"
77 attrs = {
78 k: v
79 for k, v in self.props.items()
80 if k not in ("gap", "span", "class_", "class")
81 }
82 return el("div", *children_html, class_=cls, **attrs)
85class Aside(Component):
86 """A layout component for asides/sidebars within a page."""
88 def __init__(
89 self,
90 *children,
91 position: str = "left",
92 width: str = "w-64",
93 **props,
94 ) -> None:
95 super().__init__(position=position, width=width, **props)
96 if children:
97 self.children = list(children)
98 self.position = position
99 self.width = width
101 def render(self) -> Any:
102 from lexigram.ui.core.base import raw, render_to_string
104 children_html = [
105 raw(render_to_string(c))
106 if hasattr(c, "__html__") or hasattr(c, "render")
107 else str(c)
108 for c in self.children
109 ]
110 cls = f"flex-shrink-0 {self.width} bg-card border-r border-border h-full overflow-y-auto"
111 if self.position == "right":
112 cls = cls.replace("border-r", "border-l")
114 custom_cls = self.props.get("class_", self.props.get("class"))
115 if custom_cls:
116 cls = f"{cls} {custom_cls}"
118 attrs = {
119 k: v
120 for k, v in self.props.items()
121 if k not in ("position", "width", "class_", "class")
122 }
123 return el("aside", *children_html, class_=cls, **attrs)
126class Grid(Component):
127 """Generic CSS Grid component."""
129 def __init__(
130 self,
131 *children,
132 cols: int | dict[str, int] = 1,
133 gap: int = 4,
134 **props,
135 ) -> None:
136 super().__init__(cols=cols, gap=gap, **props)
137 if children:
138 self.children = list(children)
139 self.cols = cols
140 self.gap = gap
142 def render(self) -> Any:
143 cls = "grid"
144 if isinstance(self.cols, int):
145 cls += f" grid-cols-{self.cols}"
146 elif isinstance(self.cols, dict):
147 if "default" in self.cols:
148 cls += f" grid-cols-{self.cols['default']}"
149 for break_pt, count in self.cols.items():
150 if break_pt != "default":
151 cls += f" {break_pt}:grid-cols-{count}"
153 cls += f" gap-{self.gap}"
154 custom_cls = self.props.get("class_", self.props.get("class"))
155 if custom_cls:
156 cls = f"{cls} {custom_cls}"
158 from lexigram.ui.core.base import raw, render_to_string
160 children_html = [
161 raw(render_to_string(c))
162 if hasattr(c, "__html__") or hasattr(c, "render")
163 else str(c)
164 for c in self.children
165 ]
166 attrs = {
167 k: v
168 for k, v in self.props.items()
169 if k not in ("cols", "gap", "class_", "class")
170 }
171 return el("div", *children_html, class_=cls, **attrs)
174class Stack(Component):
175 """Vertical stack component (flex column)."""
177 def __init__(self, *children, gap: int = 4, **props) -> None:
178 super().__init__(gap=gap, **props)
179 if children:
180 self.children = list(children)
181 self.gap = gap
183 def render(self) -> Any:
184 cls = f"flex flex-col gap-{self.gap}"
185 custom_cls = self.props.get("class_", self.props.get("class"))
186 if custom_cls:
187 cls = f"{cls} {custom_cls}"
189 from lexigram.ui.core.base import raw, render_to_string
191 children_html = [
192 raw(render_to_string(c))
193 if hasattr(c, "__html__") or hasattr(c, "render")
194 else str(c)
195 for c in self.children
196 ]
197 attrs = {
198 k: v for k, v in self.props.items() if k not in ("gap", "class_", "class")
199 }
200 return el("div", *children_html, class_=cls, **attrs)
203class Container(Component):
204 def __init__(self, *children, **props) -> None:
205 super().__init__(**props)
206 if children:
207 self.children = list(children)
209 def render(self) -> Any:
210 from lexigram.ui.core.base import raw, render_to_string
212 children_html = [
213 raw(render_to_string(c))
214 if hasattr(c, "__html__") or hasattr(c, "render")
215 else str(c)
216 for c in self.children
217 ]
218 cls = "max-w-7xl mx-auto px-4"
219 custom_cls = self.props.get("class_", self.props.get("class"))
220 if custom_cls:
221 cls = f"{cls} {custom_cls}"
223 attrs = {k: v for k, v in self.props.items() if k not in ("class_", "class")}
224 return el("div", *children_html, class_=cls, **attrs)