"""To experiment with layout component, move them to reflex/components later.""" from __future__ import annotations from typing import Any, List from reflex import color, cond from reflex.components.base.fragment import Fragment from reflex.components.component import Component, ComponentNamespace, MemoizationLeaf from reflex.components.radix.primitives.drawer import DrawerRoot, drawer from reflex.components.radix.themes.components.icon_button import IconButton from reflex.components.radix.themes.layout.box import Box from reflex.components.radix.themes.layout.container import Container from reflex.components.radix.themes.layout.stack import HStack from reflex.event import call_script from reflex.experimental import hooks from reflex.state import ComponentState from reflex.style import Style from reflex.vars.base import Var class Sidebar(Box, MemoizationLeaf): """A component that renders the sidebar.""" @classmethod def create(cls, *children, **props): """Create the sidebar component. Args: children: The children components. props: The properties of the sidebar. Returns: The sidebar component. """ # props.setdefault("border_right", f"1px solid {color('accent', 12)}") # props.setdefault("background_color", color("accent", 1)) # props.setdefault("width", "20vw") # props.setdefault("height", "100vh") # props.setdefault("position", "fixed") return super().create( Box.create(*children, **props), # sidebar for content Box.create(width=props.get("width")), # spacer for layout ) def add_style(self) -> dict[str, Any] | None: """Add style to the component. Returns: The style of the component. """ sidebar: Component = self.children[-2] # type: ignore spacer: Component = self.children[-1] # type: ignore open = ( self.State.open # type: ignore if self.State else Var(_js_expr="open") ) sidebar.style["display"] = spacer.style["display"] = cond(open, "block", "none") return Style( { "position": "fixed", "border_right": f"1px solid {color('accent', 12)}", "background_color": color("accent", 1), "width": "20vw", "height": "100vh", } ) def add_hooks(self) -> List[Var]: """Get the hooks to render. Returns: The hooks for the sidebar. """ return [hooks.useState("open", "true")] if not self.State else [] class StatefulSidebar(ComponentState): """Bind a state to a sidebar component.""" open: bool = True def toggle(self): """Toggle the sidebar.""" self.open = not self.open @classmethod def get_component(cls, *children, **props): """Get the stateful sidebar component. Args: children: The children components. props: The properties of the sidebar. Returns: The stateful sidebar component. """ return Sidebar.create(*children, **props) class DrawerSidebar(DrawerRoot): """A component that renders a drawer sidebar.""" @classmethod def create(cls, *children, **props): """Create the sidebar component. Args: children: The children components. props: The properties of the sidebar. Returns: The drawer sidebar component. """ direction = props.pop("direction", "left") props.setdefault("border_right", f"1px solid {color('accent', 12)}") props.setdefault("background_color", color("accent", 1)) props.setdefault("width", "20vw") props.setdefault("height", "100vh") return super().create( drawer.trigger( IconButton.create( "arrow-right-from-line", background_color="transparent", ), position="absolute", top="15", left="15", ), drawer.portal( drawer.content( *children, **props, ) ), direction=direction, ) sidebar_trigger_style = { "position": "fixed", "z_index": "15", "color": color("accent", 12), "background_color": "transparent", "padding": "0", } class SidebarTrigger(Fragment): """A component that renders the sidebar trigger.""" @classmethod def create(cls, sidebar: Component, **props): """Create the sidebar trigger component. Args: sidebar: The sidebar component. props: The properties of the sidebar trigger. Returns: The sidebar trigger component. """ trigger_props = {**props, **sidebar_trigger_style} inner_sidebar: Component = sidebar.children[0] # type: ignore sidebar_width = inner_sidebar.style.get("width") if sidebar.State: open, toggle = sidebar.State.open, sidebar.State.toggle # type: ignore else: open, toggle = ( Var(_js_expr="open"), call_script(Var(_js_expr="setOpen(!open)")), ) trigger_props["left"] = cond(open, f"calc({sidebar_width} - 32px)", "0") trigger = cond( open, IconButton.create( "arrow-left-from-line", on_click=toggle, **trigger_props, ), IconButton.create( "arrow-right-from-line", on_click=toggle, **trigger_props, ), ) return super().create(trigger) class Layout(Box): """A component that renders the layout.""" @classmethod def create( cls, *content: Component, sidebar: Component | None = None, **props, ): """Create the layout component. Args: content: The content component. sidebar: The sidebar component. props: The properties of the layout. Returns: The layout component. """ layout_root = HStack.create if sidebar is None: return Container.create(*content, **props) if isinstance(sidebar, DrawerSidebar): return super().create( sidebar, Container.create(*content, height="100%"), **props, width="100vw", min_height="100vh", ) if not isinstance(sidebar, Sidebar): sidebar = Sidebar.create(sidebar) # Add the sidebar trigger to the sidebar as first child to not mess up the rendering. sidebar.children.insert(0, SidebarTrigger.create(sidebar)) return super().create( layout_root( sidebar, Container.create(*content, height="100%"), **props, width="100vw", min_height="100vh", ) ) class LayoutNamespace(ComponentNamespace): """Namespace for layout components.""" drawer_sidebar = staticmethod(DrawerSidebar.create) stateful_sidebar = staticmethod(StatefulSidebar.create) sidebar = staticmethod(Sidebar.create) __call__ = staticmethod(Layout.create) layout = LayoutNamespace()