sidebar

Sidebar module
import fasthtml.common as fh
from fh_ui.core import *
def SidebarLayout(*args, **kwargs):
    return fh.Div(fh.Style("me { display: flex; width: 100%; min-height: 100svh; }"), *args, **kwargs)
def Sidebar(*args, **kwargs):
    return fh.Div(id="sidebar-render", data_state="expanded")(
        fh.Style("""
        me { 
            width: 16rem; 
            height: 100svh; 
            position: relative; 
            display: flex; 
            flex-direction: column; 
            border-right: 1px solid var(--zinc-200); 
            background: var(--zinc-100); 
            transition: width 200ms linear;
        }
        me[data-state="collapsed"] { width: 0; }
        me > * { opacity: 1; transition: opacity 100ms linear 100ms; }
        me[data-state="collapsed"] > * { opacity: 0; transition: opacity 100ms linear; }
        me[data-state="collapsed"] #sidebar-rail { cursor: e-resize; }
        """),
        *args, **kwargs)
def SidebarHeader(*args, **kwargs):
    return fh.Div(fh.Style("me { display: flex; flex-direction: column; gap: 0.5rem; padding: 0.5rem }"), *args, **kwargs)
def SidebarFooter(*args, **kwargs):
    return fh.Div(fh.Style("me { display: flex; flex-direction: column; gap: 0.5rem; padding: 0.5rem }"), *args, **kwargs)
def SidebarContent(*args, **kwargs):
    return fh.Div(
        fh.Style("""
        me { display: flex; min-height: 0px; flex: 1 1 0%; flex-direction: column; gap: 0.5rem; overflow: auto; }
        """), *args, **kwargs)
def SidebarRail():
    return fh.Button(
        fh.Style("""
        me {
            cursor: w-resize;
            position: absolute;
            right: -1rem;
            top: 0;
            bottom: 0;,
            z-index: 20;
            width: 1rem;
            transform: translateX(-50%);
            transition: all linear;
            /* display: none; */
            display: flex;
            background: transparent;
            border-width: 0;
        }
        
        me::after {
            content: '';
            position: absolute;
            top: 0;
            bottom: 0;
            width: 2px;
        }
        
        me:hover::after { background-color: var(--zinc-300); }
    
        """), 
        id="sidebar-rail",
        hx_on_click="htmx.find('#sidebar-render').dataset.state = htmx.find('#sidebar-render').dataset.state === 'collapsed' ? 'expanded' : 'collapsed'",
        aria_label="Toggle Sidebar", tabindex="-1", title="Toggle Sidebar")
def MainContent(*args, **kwargs):
    return fh.Div(fh.Style("me { min-height: 100svh; }"), *args, **kwargs)

Demo

hdrs
[link((),{'rel': 'preconnect', 'href': 'https://rsms.me/'}),
 link((),{'rel': 'stylesheet', 'href': 'https://rsms.me/inter/inter.css'}),
 script(('',),{'src': 'https://unpkg.com/htmx.org@next/dist/htmx.min.js'}),
 script(('',),{'src': 'https://cdn.jsdelivr.net/gh/answerdotai/fasthtml-js@1.0.4/fasthtml.js'}),
 script(('',),{'src': 'https://cdn.jsdelivr.net/gh/answerdotai/surreal@main/surreal.js'}),
 script(('',),{'src': 'https://cdn.jsdelivr.net/gh/gnat/css-scope-inline@main/script.js'}),
 style(("\n    :root {\n      --zinc-50: #fafafa;\n      --zinc-100: #f4f4f5;\n      --zinc-200: #e4e4e7;\n      --zinc-300: #d4d4d8;\n      --zinc-400: #a1a1aa;\n      --zinc-500: #71717a;\n      --zinc-600: #52525b;\n      --zinc-700: #3f3f46;\n      --zinc-800: #27272a;\n      --zinc-900: #18181b;\n      --zinc-950: #09090b;\n\n      --red-50: #fef2f2;\n      --red-100: #fee2e2;\n      --red-200: #fecaca;\n      --red-300: #fca5a5;\n      --red-400: #f87171;\n      --red-500: #ef4444;\n      --red-600: #dc2626;\n      --red-700: #b91c1c;\n      --red-800: #991b1b;\n      --red-900: #7f1d1d;\n      --red-950: #450a0a;  \n\n      font-size: 1rem;\n      font-family: Inter, sans-serif;\n      font-feature-settings: 'liga' 1, 'calt' 1; /* fix for Chrome */\n    }\n    @supports (font-variation-settings: normal) {\n        :root { font-family: InterVariable, sans-serif; }\n    }\n    ",),{})]
fh.show(*hdrs,
    SidebarLayout(
        Sidebar(
            SidebarHeader(
                fh.Style("me { border-bottom: 1px solid var(--zinc-200); }"),
                Button(fh.Style("me { anchor-name: --myAnchor; }"), "open", popover_anchor=True, popovertarget="hdr-popper", variant="outline"),
                fh.Div(id="hdr-popper", popover=True)(
                    fh.Style("me { position: absolute; position-anchor: --myAnchor; top: anchor(bottom); left: anchor(right); }"),
                    fh.P("hdr popper")
                )
            ),
            SidebarContent(fh.Ul(*[fh.Li("Content here") for _ in range(0,50)])),
            SidebarFooter(
                fh.Style("me { border-top: 1px solid var(--zinc-200); }"),
                Button("Footer here")
            ),
            SidebarRail()
        ),
        MainContent(
            fh.Style("me { flex: 1 1 0%; }"),
            fh.Header(
                fh.Style("me { display: flex; padding: 1rem; gap: 1rem; border-bottom: 1px solid var(--zinc-200); }"),
                Button(fh.NotStr("☰"), variant="ghost", hx_on_click="""
            htmx.find('#sidebar-render').dataset.state = htmx.find('#sidebar-render').dataset.state === 'collapsed' ? 'expanded' : 'collapsed'
            console.log(htmx.find('#sidebar-render').dataset.state)
            """),
                fh.Div(fh.Style("me { width: 1px; height: 100%; background: var(--zinc-300); }")),
                fh.Ol(aria_label="breadcrumb")(
                    fh.Style("""
                    me {
                        list-style: none!important;
                        padding: 0px!important;
                        margin: 0px!important;
                        display: flex;
                        flex-wrap: wrap;
                        align-items: center;
                        gap: 0.375rem; /* 1.5 * 0.25rem */
                        word-wrap: break-word;
                        font-size: 0.875rem; /* 14px */
                        color: var(--zinc-600);
                    }
                    """),
                    fh.Li("Home"), fh.Li("Specific")
                )
            ),
            fh.Div(
                fh.Style("""
                me {
                    display: flex;
                    flex-direction: column;
                    gap: 1rem;
                    padding: 1rem;
                    flex: 1 1 0%;
                }
                """),
                fh.Div(
                    fh.Style("""
                    me {
                        display: grid;
                        grid-auto-rows: min-content;
                        gap: 1rem;
                        grid-template-columns: repeat(3, 1fr);
                    }
                    """),
                    *[fh.Div(fh.Style("me { background-color: var(--zinc-50); width: 100%; min-height: 10rem; border-radius: 0.75rem; }"))] * 3
                ),
                fh.Div(fh.Style("me { background-color: var(--zinc-50); width: 100%; min-height: 20rem; border-radius: 0.75rem; }"))
            )
        )
    )
)
  1. Home
  2. Specific