Icon Rail Sidebar
blocklayoutCollapsible 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
$pnpm dlx shadcn@latest add https://uipkge.dev/r/lit/sidebar-01.json$npx shadcn@latest add https://uipkge.dev/r/lit/sidebar-01.json$yarn dlx shadcn@latest add https://uipkge.dev/r/lit/sidebar-01.json$bunx shadcn@latest add https://uipkge.dev/r/lit/sidebar-01.jsonManifest:
https://uipkge.dev/r/lit/sidebar-01.jsonInstalls to:src/uip/blocks/sidebar-01/Variants
Loading interactive previews…
Loading interactive previews…
Schema
Type aliases exported from this item's source. Use these to shape the data you pass in.
HTMLElementTagNameMapinterface 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