Menu

Change language

GitHub repo

Icon Rail Sidebar

blocklayout

Collapsible icon-rail app sidebar with brand block, two grouped nav sections, and a user dropdown footer. Pure markup, no props -- edit Sidebar01.tsx to change routes and NavUser.tsx to wire your auth session. Pair with SidebarProvider + SidebarInset for the full admin shell.

Installation

$npx shadcn@latest add https://uipkge.dev/r/lit/sidebar-01.json
Manifest:https://uipkge.dev/r/lit/sidebar-01.jsonInstalls to:src/uip/blocks/sidebar-01/

Variants

Loading interactive previews…

Schema

Type aliases exported from this item's source. Use these to shape the data you pass in.

HTMLElementTagNameMap
interface HTMLElementTagNameMap {
    'uip-sidebar-01': UipSidebar01
  }
}

Files installed (1)

  • src/uip/blocks/sidebar-01/sidebar-01.ts10.6 kB
    import { LitElement, css, html } from 'lit'
    import {
      BadgeCheck,
      BarChart3,
      BookOpen,
      ChevronsUpDown,
      FileText,
      Files,
      History,
      LayoutDashboard,
      LogOut,
      Monitor,
      Moon,
      Palette,
      Settings,
      Sun,
      Table as TableIcon,
    } from 'lucide'
    import { tailwind } from '../../lib/styles'
    import { ThemeController } from '../../lib/theme'
    import { icon } from '../../lib/icon'
    // Side-effect imports: register the composed elements for standalone use
    // (consumers who install only this block still get working primitives).
    import '../../components/avatar/avatar'
    import '../../components/dropdown-menu/dropdown-menu'
    import '../../components/sidebar/sidebar'
    
    type ThemeValue = 'light' | 'dark' | 'system'
    
    function getTheme(): ThemeValue {
      const stored = (() => {
        try {
          return localStorage.getItem('uipkge-theme')
        } catch {
          return null
        }
      })()
      if (stored === 'light' || stored === 'dark' || stored === 'system') return stored
      return document.documentElement.classList.contains('dark') ? 'dark' : 'light'
    }
    
    function setTheme(value: ThemeValue) {
      try {
        localStorage.setItem('uipkge-theme', value)
      } catch {
        // ignore
      }
      const dark = value === 'dark' || (value === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches)
      document.documentElement.classList.toggle('dark', dark)
    }
    
    function handleLogout() {
      // TODO: call your /api/auth/logout endpoint, then redirect.
      console.warn('NavUser: wire handleLogout() to your auth backend')
    }
    
    // Edit the rows below to change brand, navigation, and the user dropdown.
    // Every menu item is spelled out below -- copy, remove, or rename rows directly.
    
    /**
     * <uip-sidebar-01> — the registry Sidebar01 block as a web component
     * (NavUser inlined — one block, one install unit).
     *
     * Class strings are React's Sidebar01 + NavUser verbatim. The user details
     * are inlined (wire them to your auth session in place). The theme submenu
     * writes the `dark` class on documentElement directly (the `next-themes`
     * dependency is not ported — same visual result, persisted to localStorage).
     * No props, no events: edit the markup in place.
     */
    export class UipSidebar01 extends LitElement {
      static shadowRootOptions = { ...LitElement.shadowRootOptions, delegatesFocus: true }
      // :host display can't be a utility class (the host has no template of its own).
      static styles = [tailwind, css`:host { display: block; }`]
    
      static properties = {
        theme: { state: true },
      }
    
      private theme: ThemeValue = 'light'
    
      constructor() {
        super()
        new ThemeController(this)
      }
    
      connectedCallback() {
        super.connectedCallback()
        this.setAttribute('data-uipkge', '')
        this.setAttribute('data-slot', 'sidebar-01')
        this.theme = getTheme()
      }
    
      render() {
        return html`
          <uip-sidebar collapsible="icon">
            <uip-sidebar-header>
              <uip-sidebar-menu>
                <uip-sidebar-menu-item>
                  <uip-sidebar-menu-button size="lg" tooltip="uipkge" class="group-data-[collapsible=icon]:!justify-center">
                    <div class="bg-sidebar-primary text-sidebar-primary-foreground flex aspect-square size-8 shrink-0 items-center justify-center rounded-lg group-data-[collapsible=icon]:size-6">
                      <svg viewBox="0 0 24 24" class="size-4 group-data-[collapsible=icon]:size-3.5" fill="currentColor" aria-hidden="true">
                        <rect x="2" y="2" width="9" height="9" rx="1.5" />
                        <rect x="13" y="2" width="9" height="9" rx="1.5" opacity="0.55" />
                        <rect x="2" y="13" width="9" height="9" rx="1.5" opacity="0.55" />
                        <rect x="13" y="13" width="9" height="9" rx="1.5" />
                      </svg>
                    </div>
                    <div class="grid flex-1 text-left text-sm leading-tight group-data-[collapsible=icon]:hidden">
                      <span class="font-display truncate font-bold">uipkge</span>
                      <span class="text-muted-foreground truncate text-xs">uipkge.dev</span>
                    </div>
                  </uip-sidebar-menu-button>
                </uip-sidebar-menu-item>
              </uip-sidebar-menu>
            </uip-sidebar-header>
    
            <uip-sidebar-content>
              <uip-sidebar-group>
                <uip-sidebar-group-label>Workspace</uip-sidebar-group-label>
                <uip-sidebar-group-content>
                  <uip-sidebar-menu>
                    <uip-sidebar-menu-item>
                      <uip-sidebar-menu-button is-active tooltip="Dashboard">
                        ${icon(LayoutDashboard, 'layout-dashboard')} <span>Dashboard</span>
                      </uip-sidebar-menu-button>
                    </uip-sidebar-menu-item>
                    <uip-sidebar-menu-item>
                      <uip-sidebar-menu-button tooltip="Data tables">
                        ${icon(TableIcon, 'table')} <span>Data tables</span>
                      </uip-sidebar-menu-button>
                    </uip-sidebar-menu-item>
                    <uip-sidebar-menu-item>
                      <uip-sidebar-menu-button tooltip="Forms">
                        ${icon(FileText, 'file-text')} <span>Forms</span>
                      </uip-sidebar-menu-button>
                    </uip-sidebar-menu-item>
                    <uip-sidebar-menu-item>
                      <uip-sidebar-menu-button tooltip="Charts">
                        ${icon(BarChart3, 'chart-column')} <span>Charts</span>
                      </uip-sidebar-menu-button>
                    </uip-sidebar-menu-item>
                    <uip-sidebar-menu-item>
                      <uip-sidebar-menu-button tooltip="Pages">
                        ${icon(Files, 'files')} <span>Pages</span>
                      </uip-sidebar-menu-button>
                    </uip-sidebar-menu-item>
                  </uip-sidebar-menu>
                </uip-sidebar-group-content>
              </uip-sidebar-group>
    
              <uip-sidebar-group>
                <uip-sidebar-group-label>Resources</uip-sidebar-group-label>
                <uip-sidebar-group-content>
                  <uip-sidebar-menu>
                    <uip-sidebar-menu-item>
                      <uip-sidebar-menu-button tooltip="Docs">
                        ${icon(BookOpen, 'book-open')} <span>Docs</span>
                      </uip-sidebar-menu-button>
                    </uip-sidebar-menu-item>
                    <uip-sidebar-menu-item>
                      <uip-sidebar-menu-button tooltip="Changelog">
                        ${icon(History, 'history')} <span>Changelog</span>
                      </uip-sidebar-menu-button>
                    </uip-sidebar-menu-item>
                  </uip-sidebar-menu>
                </uip-sidebar-group-content>
              </uip-sidebar-group>
            </uip-sidebar-content>
    
            <uip-sidebar-footer>
              <uip-sidebar-menu>
                <uip-sidebar-menu-item>
                  <uip-dropdown-menu class="[&::part(content)]:min-w-56 [&::part(content)]:rounded-lg">
                    <uip-sidebar-menu-button
                      slot="trigger"
                      size="lg"
                      class="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground group-data-[collapsible=icon]:!justify-center"
                    >
                      <uip-avatar class="h-8 w-8 shrink-0 rounded-lg group-data-[collapsible=icon]:size-6">
                        <uip-avatar-fallback class="rounded-lg text-xs">TU</uip-avatar-fallback>
                      </uip-avatar>
                      <div class="grid flex-1 text-left text-sm leading-tight group-data-[collapsible=icon]:hidden">
                        <span class="truncate font-medium">Test User</span>
                        <span class="truncate text-xs">[email protected]</span>
                      </div>
                      ${icon(ChevronsUpDown, 'chevrons-up-down', 'ml-auto size-4 group-data-[collapsible=icon]:hidden')}
                    </uip-sidebar-menu-button>
                    <uip-dropdown-menu-content side="right" align="end" side-offset="4">
                      <uip-dropdown-menu-label>
                        <div class="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
                          <uip-avatar class="h-8 w-8 rounded-lg">
                            <uip-avatar-fallback class="rounded-lg">TU</uip-avatar-fallback>
                          </uip-avatar>
                          <div class="grid flex-1 text-left text-sm leading-tight">
                            <span class="truncate font-semibold">Test User</span>
                            <span class="truncate text-xs">[email protected]</span>
                          </div>
                        </div>
                      </uip-dropdown-menu-label>
                      <uip-dropdown-menu-separator></uip-dropdown-menu-separator>
                      <uip-dropdown-menu-group>
                        <uip-dropdown-menu-item>${icon(BadgeCheck, 'badge-check')} Profile</uip-dropdown-menu-item>
                        <uip-dropdown-menu-item>${icon(Settings, 'settings')} Settings</uip-dropdown-menu-item>
                      </uip-dropdown-menu-group>
                      <uip-dropdown-menu-separator></uip-dropdown-menu-separator>
                      <uip-dropdown-menu-sub>
                        <uip-dropdown-menu-sub-trigger>
                          ${icon(Palette, 'palette')} Theme
                          <span class="text-muted-foreground ml-auto text-xs capitalize">${this.theme}</span>
                        </uip-dropdown-menu-sub-trigger>
                        <uip-dropdown-menu-sub-content>
                          <uip-dropdown-menu-radio-group
                            value=${this.theme}
                            @value-change=${(e: CustomEvent<{ value: string }>) => {
                              this.theme = e.detail.value as ThemeValue
                              setTheme(this.theme)
                            }}
                          >
                            <uip-dropdown-menu-radio-item value="light">${icon(Sun, 'sun')} Light</uip-dropdown-menu-radio-item>
                            <uip-dropdown-menu-radio-item value="dark">${icon(Moon, 'moon')} Dark</uip-dropdown-menu-radio-item>
                            <uip-dropdown-menu-radio-item value="system">${icon(Monitor, 'monitor')} System</uip-dropdown-menu-radio-item>
                          </uip-dropdown-menu-radio-group>
                        </uip-dropdown-menu-sub-content>
                      </uip-dropdown-menu-sub>
                      <uip-dropdown-menu-separator></uip-dropdown-menu-separator>
                      <uip-dropdown-menu-item @select=${handleLogout}>${icon(LogOut, 'log-out')} Log out</uip-dropdown-menu-item>
                    </uip-dropdown-menu-content>
                  </uip-dropdown-menu>
                </uip-sidebar-menu-item>
              </uip-sidebar-menu>
            </uip-sidebar-footer>
    
            <uip-sidebar-rail></uip-sidebar-rail>
          </uip-sidebar>
        `
      }
    }
    
    customElements.get('uip-sidebar-01') || customElements.define('uip-sidebar-01', UipSidebar01)
    
    declare global {
      interface HTMLElementTagNameMap {
        'uip-sidebar-01': UipSidebar01
      }
    }
    

Raw manifest:https://uipkge.dev/r/lit/sidebar-01.json