""" Layout component for FastHTML applications. This component provides a responsive layout with fixed header/footer, optional collapsible left/right drawers, and a scrollable main content area. """ import logging from typing import Literal from fasthtml.common import * from myfasthtml.controls.BaseCommands import BaseCommands from myfasthtml.controls.UserProfile import UserProfile from myfasthtml.controls.helpers import mk, Ids from myfasthtml.core.commands import Command from myfasthtml.core.instances import MultipleInstance, InstancesManager 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 logger = logging.getLogger("LayoutControl") @dataclass class LayoutState: left_drawer_open: bool = True right_drawer_open: bool = False class Commands(BaseCommands): def toggle_left_drawer(self): return Command("ToggleDrawer", "Toggle main layout drawer", self._owner.toggle_drawer, "left") class Layout(MultipleInstance): """ A responsive layout component with header, footer, main content area, and optional collapsible side drawers. Attributes: app_name (str): Name of the application left_drawer (bool): Whether to include a left drawer right_drawer (bool): Whether to include a right drawer """ class DrawerContent: def __init__(self, owner, side: Literal["left", "right"]): self._owner = owner self.side = side self._content = [] def append(self, content): self._content.append(content) def get_content(self): return self._content def __init__(self, session, app_name): """ Initialize the Layout component. Args: app_name (str): Name of the application left_drawer (bool): Enable left drawer. Default is True. right_drawer (bool): Enable right drawer. Default is True. """ super().__init__(session, Ids.Layout) self.app_name = app_name # Content storage self._header_content = None self._footer_content = None self._main_content = None self._state = LayoutState() self.commands = Commands(self) self.left_drawer = self.DrawerContent(self, "left") self.right_drawer = self.DrawerContent(self, "right") 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): """ Set the main content area. Args: content: FastHTML component(s) or content for the main area """ self._main_content = content def toggle_drawer(self, side: Literal["left", "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 return self._mk_left_drawer_icon(), self._mk_left_drawer() elif side == "right": self._state.right_drawer_open = not self._state.right_drawer_open return self._mk_left_drawer_icon(), self._mk_right_drawer() else: raise ValueError("Invalid drawer side") def _mk_header(self): """ Render the header component. Returns: Header: FastHTML Header component """ return Header( self._mk_left_drawer_icon(), InstancesManager.get(self._session, Ids.UserProfile, UserProfile), cls="mf-layout-header" ) def _mk_footer(self): """ Render the footer component. Returns: Footer: FastHTML Footer component """ footer_content = self._footer_content if self._footer_content else "" return Footer( footer_content, cls="mf-layout-footer footer sm:footer-horizontal" ) def _mk_main(self): """ Render the main content area. Returns: Main: FastHTML Main component """ main_content = self._main_content if self._main_content else "" return Main( main_content, cls="mf-layout-main" ) def _mk_left_drawer(self): """ Render the left drawer if enabled. Returns: Div or None: FastHTML Div component for left drawer, or None if disabled """ return Div( *self.left_drawer.get_content(), id=f"{self._id}_ld", cls=f"mf-layout-drawer mf-layout-left-drawer {'collapsed' if not self._state.left_drawer_open else ''}", ) def _mk_right_drawer(self): """ Render the right drawer if enabled. Returns: Div or None: FastHTML Div component for right drawer, or None if disabled """ return Div( *self.right_drawer.get_content(), cls=f"mf-layout-drawer mf-layout-right-drawer {'collapsed' if not self._state.right_drawer_open else ''}", id=f"{self._id}_rd", ) def _mk_left_drawer_icon(self): return mk.icon(right_drawer_icon if self._state.left_drawer_open else left_drawer_icon, id=f"{self._id}_ldi", command=self.commands.toggle_left_drawer()) def render(self): """ Render the complete layout. Returns: Div: Complete layout as FastHTML Div component """ # Wrap everything in a container div return Div( self._mk_header(), self._mk_left_drawer(), self._mk_main(), self._mk_right_drawer(), self._mk_footer(), id=self._id, cls="mf-layout", ) def __ft__(self): """ FastHTML magic method for rendering. Returns: Div: The rendered layout """ return self.render()