# Layout Component

## Introduction

The Layout component provides a complete application structure with fixed header and footer, a scrollable main content
area, and optional collapsible side drawers. It's designed to be the foundation of your FastHTML application's UI.

**Key features:**

- Fixed header and footer that stay visible while scrolling
- Collapsible left and right drawers for navigation, tools, or auxiliary content
- Resizable drawers with drag handles
- Automatic state persistence per session
- Single instance per session (singleton pattern)

**Common use cases:**

- Application with navigation sidebar
- Dashboard with tools panel
- Admin interface with settings drawer
- Documentation site with table of contents

## Quick Start

Here's a minimal example showing an application with a navigation sidebar:

```python
from fasthtml.common import *
from myfasthtml.controls.Layout import Layout
from myfasthtml.controls.helpers import mk
from myfasthtml.core.commands import Command

# Create the layout instance
layout = Layout(parent=root_instance, app_name="My App")

# Add navigation items to the left drawer
layout.left_drawer.add(
  mk.mk(Div("Home"), command=Command(...))
)
layout.left_drawer.add(
  mk.mk(Div("About"), command=Command(...))
)
layout.left_drawer.add(
  mk.mk(Div("Contact"), command=Command(...))
)

# Set the main content
layout.set_main(
  Div(
    H1("Welcome"),
    P("This is the main content area")
  )
)

# Render the layout
return layout
```

This creates a complete application layout with:

- A header displaying the app name and drawer toggle button
- A collapsible left drawer with interactive navigation items
- A main content area that updates when navigation items are clicked
- An empty footer

**Note:** Navigation items use commands to update the main content area without page reload. See the Commands section
below for details.

## Basic Usage

### Creating a Layout

The Layout component is a `SingleInstance`, meaning there's only one instance per session. Create it by providing a
parent instance and an application name:

```python
layout = Layout(parent=root_instance, app_name="My Application")
```

### Content Zones

The Layout provides six content zones where you can add components:

```
┌──────────────────────────────────────────────────────────┐
│  Header                                                  │
│  ┌─────────────────┐              ┌─────────────────┐    │
│  │  header_left    │              │  header_right   │    │
│  └─────────────────┘              └─────────────────┘    │
├─────────┬────────────────────────────────────┬───────────┤
│         │                                    │           │
│  left   │                                    │  right    │
│ drawer  │           Main Content             │  drawer   │
│         │                                    │           │
│         │                                    │           │
├─────────┴────────────────────────────────────┴───────────┤
│  Footer                                                  │
│  ┌─────────────────┐              ┌─────────────────┐    │
│  │  footer_left    │              │  footer_right   │    │
│  └─────────────────┘              └─────────────────┘    │
└──────────────────────────────────────────────────────────┘
```

**Zone details:**

| Zone           | Typical Use                                   |
|----------------|-----------------------------------------------|
| `header_left`  | App logo, menu button, breadcrumbs            |
| `header_right` | User profile, notifications, settings         |
| `left_drawer`  | Navigation menu, tree view, filters           |
| `right_drawer` | Tools panel, properties inspector, debug info |
| `footer_left`  | Copyright, legal links, version               |
| `footer_right` | Status indicators, connection state           |

### Adding Content to Zones

Use the `.add()` method to add components to any zone:

```python
# Header
layout.header_left.add(Div("Logo"))
layout.header_right.add(Div("User: Admin"))

# Drawers
layout.left_drawer.add(Div("Navigation"))
layout.right_drawer.add(Div("Tools"))

# Footer
layout.footer_left.add(Div("© 2024 My App"))
layout.footer_right.add(Div("v1.0.0"))
```

### Setting Main Content

The main content area displays your page content and can be updated dynamically:

```python
# Set initial content
layout.set_main(
  Div(
    H1("Dashboard"),
    P("Welcome to your dashboard")
  )
)

# Update later (typically via commands)
layout.set_main(
  Div(
    H1("Settings"),
    P("Configure your preferences")
  )
)
```

### Controlling Drawers

By default, both drawers are visible. The drawer state is managed automatically:

- Users can toggle drawers using the icon buttons in the header
- Users can resize drawers by dragging the handle
- Drawer state persists within the session

The initial drawer widths are:

- Left drawer: 250px
- Right drawer: 250px

These can be adjusted by users and the state is preserved automatically.

## Content System

### Understanding Groups

Each content zone (header_left, header_right, drawers, footer) supports **groups** to organize related items. Groups are
separated visually by dividers and can have optional labels.

### Adding Content to Groups

When adding content, you can optionally specify a group name:

```python
# Add items to different groups in the left drawer
layout.left_drawer.add(Div("Dashboard"), group="main")
layout.left_drawer.add(Div("Analytics"), group="main")
layout.left_drawer.add(Div("Settings"), group="preferences")
layout.left_drawer.add(Div("Profile"), group="preferences")
```

This creates two groups:

- **main**: Dashboard, Analytics
- **preferences**: Settings, Profile

A visual divider automatically appears between groups.

### Custom Group Labels

You can provide a custom FastHTML element to display as the group header:

```python
# Add a styled group header
layout.left_drawer.add_group(
  "Navigation",
  group_ft=Div("MAIN MENU", cls="font-bold text-sm opacity-60 px-2 py-1")
)

# Then add items to this group
layout.left_drawer.add(Div("Home"), group="Navigation")
layout.left_drawer.add(Div("About"), group="Navigation")
```

### Ungrouped Content

If you don't specify a group, content is added to the default (`None`) group:

```python
# These items are in the default group
layout.left_drawer.add(Div("Quick Action 1"))
layout.left_drawer.add(Div("Quick Action 2"))
```

### Preventing Duplicates

The Content system automatically prevents adding duplicate items based on their `id` attribute:

```python
item = Div("Unique Item", id="my-item")
layout.left_drawer.add(item)
layout.left_drawer.add(item)  # Ignored - already added
```

### Group Rendering Options

Groups render differently depending on the zone:

**In drawers** (vertical layout):

- Groups stack vertically
- Dividers are horizontal lines
- Group labels appear above their content

**In header/footer** (horizontal layout):

- Groups arrange side-by-side
- Dividers are vertical lines
- Group labels are typically hidden

## Advanced Features

### Resizable Drawers

Both drawers can be resized by users via drag handles:

- **Drag handle location**:
    - Left drawer: Right edge
    - Right drawer: Left edge
- **Width constraints**: 150px (minimum) to 600px (maximum)
- **Persistence**: Resized width is automatically saved in the session state

Users can drag the handle to adjust drawer width. The new width is preserved throughout their session.

### Programmatic Drawer Control

You can control drawers programmatically using commands:

```python
# Toggle drawer visibility
toggle_left = layout.commands.toggle_drawer("left")
toggle_right = layout.commands.toggle_drawer("right")

# Update drawer width
update_left_width = layout.commands.update_drawer_width("left", width=300)
update_right_width = layout.commands.update_drawer_width("right", width=350)
```

These commands are typically used with buttons or other interactive elements:

```python
# Add a button to toggle the right drawer
button = mk.button("Toggle Tools", command=layout.commands.toggle_drawer("right"))
layout.header_right.add(button)
```

### State Persistence

The Layout automatically persists its state within the user's session:

| State Property       | Description                     | Default |
|----------------------|---------------------------------|---------|
| `left_drawer_open`   | Whether left drawer is visible  | `True`  |
| `right_drawer_open`  | Whether right drawer is visible | `True`  |
| `left_drawer_width`  | Left drawer width in pixels     | `250`   |
| `right_drawer_width` | Right drawer width in pixels    | `250`   |

State changes (toggle, resize) are automatically saved and restored within the session.

### Dynamic Content Updates

Content zones can be updated dynamically during the session:

```python
# Initial setup
layout.left_drawer.add(Div("Item 1"))


# Later, add more items (e.g., in a command handler)
def add_dynamic_content():
  layout.left_drawer.add(Div("New Item"), group="dynamic")
  return layout.left_drawer  # Return updated drawer for HTMX swap
```

**Note**: When updating content dynamically, you typically return the updated zone to trigger an HTMX swap.

### CSS Customization

The Layout uses CSS classes that you can customize:

| Class                      | Element                          |
|----------------------------|----------------------------------|
| `mf-layout`                | Root layout container            |
| `mf-layout-header`         | Header section                   |
| `mf-layout-footer`         | Footer section                   |
| `mf-layout-main`           | Main content area                |
| `mf-layout-drawer`         | Drawer container                 |
| `mf-layout-left-drawer`    | Left drawer specifically         |
| `mf-layout-right-drawer`   | Right drawer specifically        |
| `mf-layout-drawer-content` | Scrollable content within drawer |
| `mf-resizer`               | Resize handle                    |
| `mf-layout-group`          | Content group wrapper            |

You can override these classes in your custom CSS to change colors, spacing, or behavior.

### User Profile Integration

The Layout automatically includes a UserProfile component in the header right area. This component handles user
authentication display and logout functionality when auth is enabled.

## Examples

### Example 1: Dashboard with Navigation Sidebar

A typical dashboard application with a navigation menu in the left drawer:

```python
from fasthtml.common import *
from myfasthtml.controls.Layout import Layout
from myfasthtml.controls.helpers import mk
from myfasthtml.core.commands import Command

# Create layout
layout = Layout(parent=root_instance, app_name="Analytics Dashboard")


# Navigation menu in left drawer
def show_dashboard():
  layout.set_main(Div(H1("Dashboard"), P("Overview of your metrics")))
  return layout._mk_main()


def show_reports():
  layout.set_main(Div(H1("Reports"), P("Detailed analytics reports")))
  return layout._mk_main()


def show_settings():
  layout.set_main(Div(H1("Settings"), P("Configure your preferences")))
  return layout._mk_main()


# Add navigation items with groups
layout.left_drawer.add_group("main", group_ft=Div("MENU", cls="font-bold text-xs px-2 opacity-60"))
layout.left_drawer.add(mk.mk(Div("Dashboard"), command=Command("nav_dash", "Show dashboard", show_dashboard)),
                       group="main")
layout.left_drawer.add(mk.mk(Div("Reports"), command=Command("nav_reports", "Show reports", show_reports)),
                       group="main")

layout.left_drawer.add_group("config", group_ft=Div("CONFIGURATION", cls="font-bold text-xs px-2 opacity-60"))
layout.left_drawer.add(mk.mk(Div("Settings"), command=Command("nav_settings", "Show settings", show_settings)),
                       group="config")

# Header content
layout.header_left.add(Div("📊 Analytics", cls="font-bold"))

# Footer
layout.footer_left.add(Div("© 2024 Analytics Co."))
layout.footer_right.add(Div("v1.0.0"))

# Set initial main content
layout.set_main(Div(H1("Dashboard"), P("Overview of your metrics")))
```

### Example 2: Development Tool with Debug Panel

An application with development tools in the right drawer:

```python
from fasthtml.common import *
from myfasthtml.controls.Layout import Layout
from myfasthtml.controls.helpers import mk

# Create layout
layout = Layout(parent=root_instance, app_name="Dev Tools")

# Main content: code editor
layout.set_main(
  Div(
    H2("Code Editor"),
    Textarea("# Write your code here", rows=20, cls="w-full font-mono")
  )
)

# Right drawer: debug and tools
layout.right_drawer.add_group("debug", group_ft=Div("DEBUG INFO", cls="font-bold text-xs px-2 opacity-60"))
layout.right_drawer.add(Div("Console output here..."), group="debug")
layout.right_drawer.add(Div("Variables: x=10, y=20"), group="debug")

layout.right_drawer.add_group("tools", group_ft=Div("TOOLS", cls="font-bold text-xs px-2 opacity-60"))
layout.right_drawer.add(Button("Run Code"), group="tools")
layout.right_drawer.add(Button("Clear Console"), group="tools")

# Header
layout.header_left.add(Div("DevTools IDE"))
layout.header_right.add(Button("Save"))
```

### Example 3: Minimal Layout (Main Content Only)

A simple layout without drawers, focusing only on main content:

```python
from fasthtml.common import *
from myfasthtml.controls.Layout import Layout

# Create layout
layout = Layout(parent=root_instance, app_name="Simple Blog")

# Header
layout.header_left.add(Div("My Blog", cls="text-xl font-bold"))
layout.header_right.add(A("About", href="/about"))

# Main content
layout.set_main(
  Article(
    H1("Welcome to My Blog"),
    P("This is a simple blog layout without side drawers."),
    P("The focus is on the content in the center.")
  )
)

# Footer
layout.footer_left.add(Div("© 2024 Blog Author"))
layout.footer_right.add(A("RSS", href="/rss"))

# Note: Drawers are present but can be collapsed by users if not needed
```

### Example 4: Dynamic Content Loading

Loading content dynamically based on user interaction:

```python
from fasthtml.common import *
from myfasthtml.controls.Layout import Layout
from myfasthtml.controls.helpers import mk
from myfasthtml.core.commands import Command

layout = Layout(parent=root_instance, app_name="Dynamic App")


# Function that loads content dynamically
def load_page(page_name):
  # Simulate loading different content
  content = {
      "home": Div(H1("Home"), P("Welcome to the home page")),
      "profile": Div(H1("Profile"), P("User profile information")),
      "settings": Div(H1("Settings"), P("Application settings")),
  }
  layout.set_main(content.get(page_name, Div("Page not found")))
  return layout._mk_main()


# Create navigation commands
pages = ["home", "profile", "settings"]
for page in pages:
  cmd = Command(f"load_{page}", f"Load {page} page", load_page, page)
  layout.left_drawer.add(
    mk.mk(Div(page.capitalize()), command=cmd)
  )

# Set initial content
layout.set_main(Div(H1("Home"), P("Welcome to the home page")))
```

---

## Developer Reference

This section contains technical details for developers working on the Layout component itself.

### State

The Layout component maintains the following state properties:

| Name                 | Type    | Description                      | Default |
|----------------------|---------|----------------------------------|---------|
| `left_drawer_open`   | boolean | True if the left drawer is open  | True    |
| `right_drawer_open`  | boolean | True if the right drawer is open | True    |
| `left_drawer_width`  | integer | Width of the left drawer         | 250     |
| `right_drawer_width` | integer | Width of the right drawer        | 250     |

### Commands

Available commands for programmatic control:

| Name                                    | Description                                                                            |
|-----------------------------------------|----------------------------------------------------------------------------------------|
| `toggle_drawer(side)`                   | Toggles the drawer on the specified side                                               |
| `update_drawer_width(side, width=None)` | Updates the drawer width on the specified side. The width is given by the HTMX request |

### Public Methods

| Method              | Description                 |
|---------------------|-----------------------------|
| `set_main(content)` | Sets the main content area  |
| `render()`          | Renders the complete layout |

### High Level Hierarchical Structure

```
Div(id="layout")
├── Header
│   ├── Div(id="layout_hl")
│   │   ├── Icon                # Left drawer icon button
│   │   └── Div                 # Left content for the header
│   └── Div(id="layout_hr")
│       ├── Div                 # Right content for the header
│       └── UserProfile         # user profile icon button
├── Div                         # Left Drawer
├── Main                        # Main content
├── Div                         # Right Drawer
├── Footer                      # Footer
└── Script                      # To initialize the resizing
```

### Element IDs

| Name        | Description                         |
|-------------|-------------------------------------|
| `layout`    | Root layout container (singleton)   |
| `layout_h`  | Header section (not currently used) |
| `layout_hl` | Header left side                    |
| `layout_hr` | Header right side                   |
| `layout_f`  | Footer section (not currently used) |
| `layout_fl` | Footer left side                    |
| `layout_fr` | Footer right side                   |
| `layout_ld` | Left drawer                         |
| `layout_rd` | Right drawer                        |

### Internal Methods

These methods are used internally for rendering:

| Method                    | Description                                            |
|---------------------------|--------------------------------------------------------|
| `_mk_header()`            | Renders the header component                           |
| `_mk_footer()`            | Renders the footer component                           |
| `_mk_main()`              | Renders the main content area                          |
| `_mk_left_drawer()`       | Renders the left drawer                                |
| `_mk_right_drawer()`      | Renders the right drawer                               |
| `_mk_left_drawer_icon()`  | Renders the left drawer toggle icon                    |
| `_mk_right_drawer_icon()` | Renders the right drawer toggle icon                   |
| `_mk_content_wrapper()`   | Static method to wrap content with groups and dividers |

### Content Class

The `Layout.Content` nested class manages content zones:

| Method                            | Description                                              |
|-----------------------------------|----------------------------------------------------------|
| `add(content, group=None)`        | Adds content to a group, prevents duplicates based on ID |
| `add_group(group, group_ft=None)` | Creates a new group with optional custom header element  |
| `get_content()`                   | Returns dictionary of groups and their content           |
| `get_groups()`                    | Returns list of (group_name, group_ft) tuples            |