# 📱 App Pages


<!-- WARNING: THIS FILE WAS AUTOGENERATED! DO NOT EDIT! -->

## 🎯 Overview

<table>
<colgroup>
<col style="width: 32%" />
<col style="width: 38%" />
<col style="width: 29%" />
</colgroup>
<thead>
<tr>
<th>Category</th>
<th>Components</th>
<th>Purpose</th>
</tr>
</thead>
<tbody>
<tr>
<td>🔐 Auth</td>
<td><a
href="https://abhisheksreesaila.github.io/fh-matui/app_pages.html#loginscreen"><code>LoginScreen</code></a></td>
<td>Split-screen OAuth login with brand customization</td>
</tr>
<tr>
<td>📐 Layout</td>
<td><a
href="https://abhisheksreesaila.github.io/fh-matui/app_pages.html#toplayout"><code>TopLayout</code></a></td>
<td>Top-nav-only app shell with centered or full-width content</td>
</tr>
</tbody>
</table>

> **Note:** For sidebar-based layouts with navigation rail, use
> [`Layout`](https://abhisheksreesaila.github.io/fh-matui/components.html#layout)
> from `fh_matui.components`.

<details class="code-fold">
<summary>Code</summary>

``` python
from fasthtml.jupyter import *
from IPython.display import HTML, Markdown, Image
import socket
import time
import subprocess
import importlib

# Force reload to pick up latest changes
import fh_matui.foundations
import fh_matui.core
import fh_matui.components
importlib.reload(fh_matui.foundations)
importlib.reload(fh_matui.core)
importlib.reload(fh_matui.components)
from fh_matui.components import *

def kill_process_on_port(port):
    """Kill any process using the specified port on Windows"""
    try:
        # Find process using the port
        result = subprocess.run(
            f'netstat -ano | findstr :{port}',
            shell=True, capture_output=True, text=True
        )
        
        if result.stdout:
            # Extract PID from netstat output
            lines = result.stdout.strip().split('\n')
            for line in lines:
                if 'LISTENING' in line:
                    pid = line.strip().split()[-1]
                    subprocess.run(f'taskkill /PID {pid} /F', shell=True, capture_output=True)
                    print(f"✓ Killed process {pid} on port {port}")
                    time.sleep(0.5)
                    return True
        return False
    except Exception as e:
        print(f"⚠ Could not kill process on port {port}: {e}")
        return False

def find_available_port(start_port=3333, max_attempts=10):
    """Find an available port starting from start_port"""
    for port in range(start_port, start_port + max_attempts):
        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
            try:
                s.bind(('', port))
                return port
            except OSError:
                continue
    raise RuntimeError(f"Could not find an available port in range {start_port}-{start_port+max_attempts}")

# Stop existing server if running
if 'server' in globals(): 
    try:
        server.stop()
        time.sleep(0.5)
    except:
        pass

# Try to kill any process on preferred port, then find available port
preferred_port = 9998
kill_process_on_port(preferred_port)
port = find_available_port(preferred_port)

app = FastHTML(hdrs=(MatTheme.blue.headers(title="fastmaterial", mode="dark")))
rt = app.route

try:
    server = JupyUvi(app, port=port)
    preview = partial(HTMX, app=app, port=port)
    print(f"✓ Server running on port {port}")
except Exception as e:
    print(f"✗ Failed to start server: {e}")
    raise
```

</details>

<script>
document.body.addEventListener('htmx:configRequest', (event) => {
    if(event.detail.path.includes('://')) return;
    htmx.config.selfRequestsOnly=false;
    event.detail.path = `${location.protocol}//${location.hostname}:9998${event.detail.path}`;
});
</script>

    ✓ Server running on port 9998

## 🔐 Login Page

<table>
<colgroup>
<col style="width: 55%" />
<col style="width: 45%" />
</colgroup>
<thead>
<tr>
<th>Component</th>
<th>Purpose</th>
</tr>
</thead>
<tbody>
<tr>
<td><a
href="https://abhisheksreesaila.github.io/fh-matui/app_pages.html#loginscreen"><code>LoginScreen</code></a></td>
<td>Split-screen OAuth login with configurable branding</td>
</tr>
</tbody>
</table>

**Features:** OAuth provider buttons, customizable left panel,
responsive layout (stacks on mobile)

------------------------------------------------------------------------

<a
href="https://github.com/abhisheksreesaila/fh-matui/blob/master/fh_matui/app_pages.py#L148"
target="_blank" style="float:right; font-size:smaller">source</a>

### LoginScreen

``` python

def LoginScreen(
    title:str='Sign In', subtitle:str='Choose your preferred sign-in method', providers:NoneType=None,
    left_slot:NoneType=None, logo_src:NoneType=None,
    brand_name:NoneType=None, # Brand name displayed at top-left of left panel
    quotes:NoneType=None, # List of quotes to rotate (uses defaults if None, pass [] to disable)
    color_cycle:bool=True, # Enable color cycling animation
    testimonial_text:NoneType=None, # Legacy: single testimonial (use quotes instead)
    brand_bg_cls:str='primary', # Legacy: fallback if gradient fails
    left_cols:int=9, # Number of columns for left side (out of 12)
    cls:str='', kwargs:VAR_KEYWORD
):

```

*A configurable Split Login Screen with dynamic branding.*

Features: - Hero-style gradient background (same as landing page) -
Brand name + logo at top-left corner - Rotating inspirational quotes in
center (pure CSS, hidden on mobile) - Optional color cycling through
BeerCSS theme colors (minimal JS)

Args: title: Sign-in form title subtitle: Sign-in form subtitle
providers: List of OAuth provider dicts \[{label, icon, href, cls}, …\]
left_slot: Custom content for left panel (overrides default branding)
logo_src: URL/path to logo image brand_name: Brand name displayed at
top-left corner quotes: List of quote strings to rotate. Defaults to
inspirational quotes. Pass empty list \[\] to disable quotes entirely.
color_cycle: Enable gradient color cycling (default True) left_cols:
Grid columns for left panel (out of 12). Default 9 = 75%

Example: LoginScreen( brand_name=“MyApp”, logo_src=“/static/logo.svg”,
quotes=\[ “Your custom quote here — Author”, “Another inspiring message
— Source”, \], )

<details class="code-fold">
<summary>Code</summary>

``` python
preview(LoginScreen())
```

</details>

<iframe src="http://localhost:9998/_oaUCQQDYTiWOEd8vrTVVzQ" style="width: 100%; height: auto; border: none;" onload="{
        let frame = this;
        window.addEventListener('message', function(e) {
            if (e.source !== frame.contentWindow) return; // Only proceed if the message is from this iframe
            if (e.data.height) frame.style.height = (e.data.height+1) + 'px';
        }, false);
    }" allow="accelerometer; autoplay; camera; clipboard-read; clipboard-write; display-capture; encrypted-media; fullscreen; gamepad; geolocation; gyroscope; hid; identity-credentials-get; idle-detection; magnetometer; microphone; midi; payment; picture-in-picture; publickey-credentials-get; screen-wake-lock; serial; usb; web-share; xr-spatial-tracking"></iframe> 

<details class="code-fold">
<summary>Code</summary>

``` python
@app.get("/test-login")
def login():
    return LoginScreen()
```

</details>

## 📐 TopLayout

A simple **top-navigation-only** app shell — no sidebar, no navigation
rail.

Use this when your app navigates via top navbar links (e.g. settings
pages, data tables, checkout flows, or analytics dashboards).

<table>
<colgroup>
<col style="width: 37%" />
<col style="width: 31%" />
<col style="width: 31%" />
</colgroup>
<thead>
<tr>
<th>Parameter</th>
<th>Default</th>
<th>Purpose</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>nav_bar</code></td>
<td><code>None</code></td>
<td>A <code>NavBar(...)</code> instance for top navigation</td>
</tr>
<tr>
<td><code>main_id</code></td>
<td><code>'main-content'</code></td>
<td>ID for the content area (use as <code>hx_target</code>)</td>
</tr>
<tr>
<td><code>main_bg</code></td>
<td><code>'surface'</code></td>
<td>Background class for the main content area</td>
</tr>
</tbody>
</table>

Content always stretches full-width inside the `<main>` area.

### NavBar Configuration for TopLayout

Configure these **NavBar parameters** for optimal SPA-style navigation:

<table>
<colgroup>
<col style="width: 33%" />
<col style="width: 39%" />
<col style="width: 27%" />
</colgroup>
<thead>
<tr>
<th>Parameter</th>
<th>Recommended</th>
<th>Purpose</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>sticky</code></td>
<td><code>True</code></td>
<td>Keep navbar visible while scrolling</td>
</tr>
<tr>
<td><code>blur</code></td>
<td><code>'small-blur'</code> or <code>'large-blur'</code></td>
<td>Glass effect (transparent navbar)</td>
</tr>
<tr>
<td><code>hx_swap</code></td>
<td><code>'outerHTML'</code></td>
<td>Replace entire <code>&lt;main&gt;</code> on navigation</td>
</tr>
</tbody>
</table>

``` python
def my_navbar():
    return NavBar(
        A("Dashboard", href="/dashboard"),
        A("Settings", href="/settings"),
        brand=H5("MyApp", cls="bold"),
        sticky=True,           # Stick to top
        blur='small-blur',     # Glass effect
        hx_swap='outerHTML',   # Full main swap for SPA navigation
        cls="primary"          # Theme color
    )
```

> 💡 **Note:** Configure `sticky`, `blur`, and `hx_swap` directly on the
> NavBar — TopLayout passes the navbar through as-is.

------------------------------------------------------------------------

<a
href="https://github.com/abhisheksreesaila/fh-matui/blob/master/fh_matui/app_pages.py#L333"
target="_blank" style="float:right; font-size:smaller">source</a>

### TopLayout

``` python

def TopLayout(
    content:VAR_POSITIONAL, nav_bar:NoneType=None, main_id:str='main-content', main_bg:str='surface',
    kwargs:VAR_KEYWORD
):

```

*Top-navigation-only app shell — full-width content below a sticky
NavBar.*

Returns `(nav, main)` as a tuple so they render as **sibling elements**
directly under `<body>`. This is required for BeerCSS’s native layout
engine.

Configure the NavBar with `sticky=True`, `blur='small-blur'`, and
`hx_swap='outerHTML'` for the recommended glass-effect navigation.

The `<main>` element has `padding-top: 4.5rem` (navbar height) to
prevent content from being hidden under the sticky navbar, with iOS
safe-area support.

Use
`[`TopContent`](https://abhisheksreesaila.github.io/fh-matui/app_pages.html#topcontent)`
to wrap HTMX partial responses.

Args: content: Page content (children) nav_bar: A `NavBar(...)`
instance. Configure sticky/blur/hx_swap on NavBar directly. main_id: ID
for the `<main>` content area (default `'main-content'`) main_bg:
Background class for the main area (default `'surface'`)

Example::

    TopLayout(
        H1("Dashboard"), DashboardGrid(),
        nav_bar=NavBar(
            A("Home", href="/"),
            brand=H5("MyApp"),
            sticky=True,
            blur='small-blur',
            hx_swap='outerHTML'
        ),
    )

Route pattern (use TopContent for HTMX partials)::

    @rt("/dashboard")
    def dashboard(req):
        page = DashboardGrid()
        if 'HX-Request' in req.headers:
            return TopContent(page)
        return TopLayout(page, nav_bar=my_navbar())

------------------------------------------------------------------------

<a
href="https://github.com/abhisheksreesaila/fh-matui/blob/master/fh_matui/app_pages.py#L298"
target="_blank" style="float:right; font-size:smaller">source</a>

### TopContent

``` python

def TopContent(
    content:VAR_POSITIONAL, main_id:str='main-content', main_bg:str='surface'
):

```

*Wrap content for HTMX partial responses inside a TopLayout.*

Returns a full-width `<main>` element with padding-top offset for sticky
navbars. Use with `hx-swap="outerHTML"` on the NavBar.

If content is already a `<main>` element, returns it directly to prevent
double-wrapping.

Args: content: Page content (children) main_id: Must match the `main_id`
used in TopLayout (default `'main-content'`) main_bg: Background class
(default `'surface'`)

Example::

    @rt("/dashboard")
    def get(req):
        page = dashboard_content()
        if 'HX-Request' in req.headers:
            return TopContent(page)
        return TopLayout(page, nav_bar=my_navbar())

<details class="code-fold">
<summary>Code</summary>

``` python
# --- Working TopLayout example with HTMX SPA navigation ---

# 1. Shared navbar — blue with large-blur glass effect
def my_navbar():
    return NavBar(
        A("Dashboard", href="/dashboard"),
        A("Settings", href="/settings"),
        A("Profile", href="/profile"),
        brand=H5("MyApp", cls="bold"),
        sticky=True,
        blur='large-blur',
        hx_swap='outerHTML',
        cls="primary"
    )

# 2. Helper: center content using a 3-column grid (empty | content | empty)
def centered_page(*content):
    """Wrap content in a 3-col grid so it sits in the center column.
    s12 = full-width on mobile, l6 = 6/12 on desktop (centered)."""
    return Grid(
        GridCell(span="s0 m2 l3"),           # left spacer (hidden on small)
        GridCell(*content, span="s12 m8 l6"), # center content
        GridCell(span="s0 m2 l3"),           # right spacer (hidden on small)
    )

# 3. Page content functions
def dashboard_content():
    """Full-width — no centering grid, content fills the page."""
    return Div(
        H3("Analytics Dashboard"),
        Div(
            P("Full-width content area — stretches edge to edge", cls="center-align"),
            cls="primary-container padding round",
            style="min-height:60px;"
        ),
        Grid(
            Card(H5("Revenue"), P("$12,345"), cls="padding"),
            Card(H5("Users"), P("1,234"), cls="padding"),
            Card(H5("Orders"), P("567"), cls="padding"),
            Card(H5("Conversion"), P("4.2%"), cls="padding"),
            cols=4
        ),
    )

def settings_content():
    """Centered — uses the 3-col grid trick for readability."""
    return centered_page(
        H3("Account Settings"),
        P("Update your profile and preferences."),
        Card(
            LabelInput(label="Display Name", id="name"),
            LabelInput(label="Email", id="email", input_type="email"),
            Button("Save", cls=ButtonT.primary),
            cls="padding"
        )
    )

def profile_content():
    """Centered — uses the 3-col grid trick for readability."""
    return centered_page(
        H3("Profile"),
        Card(
            DivHStacked(
                I("person", cls="circle extra primary-container"),
                DivVStacked(H5("John Doe"), P("john@example.com", cls="small-text"))
            ),
            cls="padding"
        )
    )

# 4. Routes — all full-width <main>, centering handled by content itself
@rt("/dashboard")
def get(req):
    content = dashboard_content()
    if 'HX-Request' in req.headers:
        return TopContent(content)
    return TopLayout(content, nav_bar=my_navbar())

@rt("/settings")
def get(req):
    content = settings_content()
    if 'HX-Request' in req.headers:
        return TopContent(content)
    return TopLayout(content, nav_bar=my_navbar())

@rt("/profile")
def get(req):
    content = profile_content()
    if 'HX-Request' in req.headers:
        return TopContent(content)
    return TopLayout(content, nav_bar=my_navbar())

# 5. Preview — full-width dashboard
preview(TopLayout(dashboard_content(), nav_bar=my_navbar()))
```

</details>

<iframe src="http://localhost:9998/_Cvx6mp0OTDWL67MmogmNXw" style="width: 100%; height: auto; border: none;" onload="{
        let frame = this;
        window.addEventListener('message', function(e) {
            if (e.source !== frame.contentWindow) return; // Only proceed if the message is from this iframe
            if (e.data.height) frame.style.height = (e.data.height+1) + 'px';
        }, false);
    }" allow="accelerometer; autoplay; camera; clipboard-read; clipboard-write; display-capture; encrypted-media; fullscreen; gamepad; geolocation; gyroscope; hid; identity-credentials-get; idle-detection; magnetometer; microphone; midi; payment; picture-in-picture; publickey-credentials-get; screen-wake-lock; serial; usb; web-share; xr-spatial-tracking"></iframe> 
