Added Controls testing + documentation

This commit is contained in:
2025-12-05 19:17:21 +01:00
parent 1d20fb8650
commit 8e5fa7f752
37 changed files with 4868 additions and 363 deletions

View File

@@ -17,15 +17,17 @@ from myfasthtml.core.commands import Command
from myfasthtml.core.dbmanager import DbObject
from myfasthtml.core.instances import SingleInstance
from myfasthtml.core.utils import get_id
from myfasthtml.icons.fluent import panel_left_expand20_regular as left_drawer_icon
from myfasthtml.icons.fluent_p2 import panel_right_expand20_regular as right_drawer_icon
from myfasthtml.icons.fluent import panel_left_contract20_regular as left_drawer_contract
from myfasthtml.icons.fluent import panel_left_expand20_regular as left_drawer_expand
from myfasthtml.icons.fluent_p1 import panel_right_contract20_regular as right_drawer_contract
from myfasthtml.icons.fluent_p2 import panel_right_expand20_regular as right_drawer_expand
logger = logging.getLogger("LayoutControl")
class LayoutState(DbObject):
def __init__(self, owner):
super().__init__(owner)
def __init__(self, owner, name=None):
super().__init__(owner, name=name)
with self.initializing():
self.left_drawer_open: bool = True
self.right_drawer_open: bool = True
@@ -37,12 +39,13 @@ class Commands(BaseCommands):
def toggle_drawer(self, side: Literal["left", "right"]):
return Command("ToggleDrawer", f"Toggle {side} layout drawer", self._owner.toggle_drawer, side)
def update_drawer_width(self, side: Literal["left", "right"]):
def update_drawer_width(self, side: Literal["left", "right"], width: int = None):
"""
Create a command to update drawer width.
Args:
side: Which drawer to update ("left" or "right")
width: New width in pixels. Given by the HTMX request
Returns:
Command: Command object for updating drawer width
@@ -114,7 +117,7 @@ class Layout(SingleInstance):
# Content storage
self._main_content = None
self._state = LayoutState(self)
self._state = LayoutState(self, "default_layout")
self._boundaries = Boundaries(self)
self.commands = Commands(self)
self.left_drawer = self.Content(self)
@@ -123,16 +126,6 @@ class Layout(SingleInstance):
self.header_right = self.Content(self)
self.footer_left = self.Content(self)
self.footer_right = self.Content(self)
self._footer_content = None
def set_footer(self, content):
"""
Set the footer content.
Args:
content: FastHTML component(s) or content for the footer
"""
self._footer_content = content
def set_main(self, content):
"""
@@ -145,6 +138,18 @@ class Layout(SingleInstance):
return self
def toggle_drawer(self, side: Literal["left", "right"]):
"""
Toggle the state of a drawer (open or close) based on the specified side. This
method also generates the corresponding icon and drawer elements for the
selected side.
:param side: The side of the drawer to toggle. Must be either "left" or "right".
:type side: Literal["left", "right"]
:return: A tuple containing the updated drawer icon and drawer elements for
the specified side.
:rtype: Tuple[Any, Any]
:raises ValueError: If the provided `side` is not "left" or "right".
"""
logger.debug(f"Toggle drawer: {side=}, {self._state.left_drawer_open=}")
if side == "left":
self._state.left_drawer_open = not self._state.left_drawer_open
@@ -190,15 +195,17 @@ class Layout(SingleInstance):
return Header(
Div( # left
self._mk_left_drawer_icon(),
*self.header_left.get_content(),
cls="flex gap-1"
*self._mk_content_wrapper(self.header_left, horizontal=True, show_group_name=False).children,
cls="flex gap-1",
id=f"{self._id}_hl"
),
Div( # right
*self.header_right.get_content()[None],
*self._mk_content_wrapper(self.header_right, horizontal=True, show_group_name=False).children,
UserProfile(self),
cls="flex gap-1"
cls="flex gap-1",
id=f"{self._id}_hr"
),
cls="mf-layout-header"
cls="mf-layout-header",
)
def _mk_footer(self):
@@ -208,9 +215,17 @@ class Layout(SingleInstance):
Returns:
Footer: FastHTML Footer component
"""
footer_content = self._footer_content if self._footer_content else ""
return Footer(
footer_content,
Div( # left
*self._mk_content_wrapper(self.footer_left, horizontal=True, show_group_name=False).children,
cls="flex gap-1",
id=f"{self._id}_fl"
),
Div( # right
*self._mk_content_wrapper(self.footer_right, horizontal=True, show_group_name=False).children,
cls="flex gap-1",
id=f"{self._id}_fr"
),
cls="mf-layout-footer footer sm:footer-horizontal"
)
@@ -277,7 +292,14 @@ class Layout(SingleInstance):
# Wrap content in scrollable container
content_wrapper = Div(
*self.right_drawer.get_content(),
*[
(
Div(cls="divider") if index > 0 else None,
group_ft,
*[item for item in self.right_drawer.get_content()[group_name]]
)
for index, (group_name, group_ft) in enumerate(self.right_drawer.get_groups())
],
cls="mf-layout-drawer-content"
)
@@ -290,15 +312,29 @@ class Layout(SingleInstance):
)
def _mk_left_drawer_icon(self):
return mk.icon(right_drawer_icon if self._state.left_drawer_open else left_drawer_icon,
return mk.icon(left_drawer_contract if self._state.left_drawer_open else left_drawer_expand,
id=f"{self._id}_ldi",
command=self.commands.toggle_drawer("left"))
def _mk_right_drawer_icon(self):
return mk.icon(right_drawer_icon if self._state.left_drawer_open else left_drawer_icon,
return mk.icon(right_drawer_contract if self._state.right_drawer_open else right_drawer_expand,
id=f"{self._id}_rdi",
command=self.commands.toggle_drawer("right"))
@staticmethod
def _mk_content_wrapper(content: Content, show_group_name: bool = True, horizontal: bool = False):
return Div(
*[
(
Div(cls=f"divider {'divider-horizontal' if horizontal else ''}") if index > 0 else None,
group_ft if show_group_name else None,
*[item for item in content.get_content()[group_name]]
)
for index, (group_name, group_ft) in enumerate(content.get_groups())
],
cls="mf-layout-drawer-content"
)
def render(self):
"""
Render the complete layout.
@@ -309,12 +345,13 @@ class Layout(SingleInstance):
# Wrap everything in a container div
return Div(
Div(id=f"tt_{self._id}", cls="mf-tooltip-container"), # container for the tooltips
self._mk_header(),
self._mk_left_drawer(),
self._mk_main(),
self._mk_right_drawer(),
self._mk_footer(),
Script(f"initResizer('{self._id}');"),
Script(f"initLayout('{self._id}');"),
id=self._id,
cls="mf-layout",
)