Toolbar Action Sidebar
blocklayoutApp sidebar with a bottom action bar -- workspace pill, ⌘K search, grouped nav with section labels (Dashboard / Management / Content), badge counts, user pill above an icon toolbar (Settings, Help, Notifications, Command, Log out). Built on sidebar-01.
Installation
$pnpm dlx shadcn@latest add https://uipkge.dev/r/lit/sidebar-07.json$npx shadcn@latest add https://uipkge.dev/r/lit/sidebar-07.json$yarn dlx shadcn@latest add https://uipkge.dev/r/lit/sidebar-07.json$bunx shadcn@latest add https://uipkge.dev/r/lit/sidebar-07.jsonManifest:
https://uipkge.dev/r/lit/sidebar-07.jsonInstalls to:src/uip/blocks/sidebar-07/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-07': UipSidebar07
}
}Files installed (1)
src/uip/blocks/sidebar-07/sidebar-07.ts12.4 kB
import { LitElement, css, html } from 'lit' import { Bell, ChartBar, ChevronsUpDown, Command, CreditCard, FileText, HelpCircle, LineChart, Mail, Moon, Package, Plus, Search, Settings as SettingsIcon, ShoppingCart, Sun, Users, } 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/input/input' import type { UipInput } from '../../components/input/input' import '../../components/sidebar/sidebar' import type { UipSidebarProvider } from '../../components/sidebar/sidebar' function isDarkActive(): boolean { return document.documentElement.classList.contains('dark') } function setDark(dark: boolean) { document.documentElement.classList.toggle('dark', dark) try { localStorage.setItem('uipkge-theme', dark ? 'dark' : 'light') } catch { // ignore } } // App sidebar with a bottom action bar. Workspace pill + search at the top, // three grouped nav sections, user pill above an icon toolbar (Settings, // Help, Notifications, Command palette, theme toggle) anchored at the very // bottom. Every row is spelled out inline so you can edit in place. /** * <uip-sidebar-07> — the registry Sidebar07 block as a web component. * * Class strings are React's verbatim. The footer toolbar follows the * provider's state (React's useSidebar `state`); the block re-renders when * the provider's `open-change` fires. The search field is <uip-input> with * `prefix`/`suffix` slots (React's SidebarInput prefix/suffix props). The * theme toggle writes the `dark` class on documentElement directly (the * `next-themes` dependency is not ported). Deviations: the toolbar's last * button is React's SidebarTrigger (collapse toggle) — ported as * <uip-sidebar-trigger>, which carries its own trigger styling on top of * React's row classes. No props, no events: edit the markup in place. */ export class UipSidebar07 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 = { isDark: { state: true }, query: { state: true }, } private isDark = false private query = '' private media?: MediaQueryList constructor() { super() new ThemeController(this) } connectedCallback() { super.connectedCallback() this.setAttribute('data-uipkge', '') this.setAttribute('data-slot', 'sidebar-07') this.isDark = isDarkActive() // Re-render when the provider opens/closes or the viewport crosses the // mobile breakpoint (React's useSidebar re-render). this.addEventListener('open-change', this.rerender) this.media = window.matchMedia('(max-width: 768px)') this.media.addEventListener('change', this.rerender) } disconnectedCallback() { super.disconnectedCallback() this.removeEventListener('open-change', this.rerender) this.media?.removeEventListener('change', this.rerender) } private rerender = () => this.requestUpdate() private toolbarButton(label: string, iconName: string, iconNode: typeof SettingsIcon, onClick?: () => void) { return html` <button type="button" aria-label=${label} class="text-muted-foreground hover:bg-sidebar-accent hover:text-foreground flex aspect-square size-8 items-center justify-center rounded-md transition-colors" @click=${() => onClick?.()} > ${icon(iconNode, iconName, 'size-4')} </button> ` } render() { const expanded = (this.closest('uip-sidebar-provider') as UipSidebarProvider | null)?.state !== 'collapsed' 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-semibold">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> <div class="px-1 pt-1 group-data-[collapsible=icon]:hidden"> <uip-input type="text" placeholder="Search..." allow-clear class="[&::part(control)]:bg-background [&::part(control)]:h-8 [&::part(control)]:shadow-none" .value=${this.query} @input=${(e: Event) => { this.query = (e.target as UipInput).value }} > <span slot="prefix">${icon(Search, 'search', 'size-3.5')}</span> <kbd slot="suffix" class="bg-muted text-muted-foreground pointer-events-none inline-flex h-5 items-center gap-0.5 rounded border px-1 font-mono text-xs font-medium select-none"> <span>⌘</span>K </kbd> </uip-input> </div> </uip-sidebar-header> <uip-sidebar-content> <uip-sidebar-group> <uip-sidebar-group-label>Dashboard</uip-sidebar-group-label> <uip-sidebar-group-content> <uip-sidebar-menu> <uip-sidebar-menu-item> <uip-sidebar-menu-button is-active tooltip="Analytics" class="[&[is-active]::part(base)]:!bg-success/20 [&[is-active]::part(base)]:!text-foreground" > ${icon(ChartBar, 'chart-bar', 'size-4')} <span>Analytics</span> </uip-sidebar-menu-button> </uip-sidebar-menu-item> <uip-sidebar-menu-item> <uip-sidebar-menu-button tooltip="Performance"> ${icon(LineChart, 'chart-line', 'size-4')} <span>Performance</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>Management</uip-sidebar-group-label> <uip-sidebar-group-content> <uip-sidebar-menu> <uip-sidebar-menu-item> <uip-sidebar-menu-button tooltip="Products"> ${icon(Package, 'package', 'size-4')} <span>Products</span> </uip-sidebar-menu-button> </uip-sidebar-menu-item> <uip-sidebar-menu-item> <uip-sidebar-menu-button tooltip="Orders"> ${icon(ShoppingCart, 'shopping-cart', 'size-4')} <span>Orders</span> </uip-sidebar-menu-button> </uip-sidebar-menu-item> <uip-sidebar-menu-item> <uip-sidebar-menu-button tooltip="Subscribers"> ${icon(Users, 'users', 'size-4')} <span>Subscribers</span> </uip-sidebar-menu-button> </uip-sidebar-menu-item> <uip-sidebar-menu-item> <uip-sidebar-menu-button tooltip="Transactions"> ${icon(CreditCard, 'credit-card', 'size-4')} <span>Transactions</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>Content</uip-sidebar-group-label> <uip-sidebar-group-action aria-label="Add content"> ${icon(Plus, 'plus', 'size-4')} </uip-sidebar-group-action> <uip-sidebar-group-content> <uip-sidebar-menu> <uip-sidebar-menu-item> <uip-sidebar-menu-button tooltip="Posts"> ${icon(FileText, 'file-text', 'size-4')} <span>Posts</span> </uip-sidebar-menu-button> <uip-sidebar-menu-badge class="[&::part(base)]:bg-destructive [&::part(base)]:text-white">10</uip-sidebar-menu-badge> </uip-sidebar-menu-item> <uip-sidebar-menu-item> <uip-sidebar-menu-button tooltip="Pages"> ${icon(Mail, 'mail', 'size-4')} <span>Pages</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-sidebar-menu-button size="lg" class="group-data-[collapsible=icon]:!justify-center"> <uip-avatar class="size-8 rounded-lg group-data-[collapsible=icon]:size-6"> <uip-avatar-fallback class="bg-sidebar-accent text-sidebar-accent-foreground rounded-lg text-xs">SW</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">Sarah Wilson</span> <span class="text-muted-foreground truncate text-xs">Admin Account</span> </div> ${icon(ChevronsUpDown, 'chevrons-up-down', 'ml-auto size-4 group-data-[collapsible=icon]:hidden')} </uip-sidebar-menu-button> </uip-sidebar-menu-item> </uip-sidebar-menu> ${expanded ? html` <uip-sidebar-separator class="[&::part(base)]:my-1"></uip-sidebar-separator> <div class="flex items-center justify-between px-1 pb-1"> ${this.toolbarButton('Settings', 'settings', SettingsIcon)} ${this.toolbarButton('Help', 'circle-question-mark', HelpCircle)} ${this.toolbarButton('Notifications', 'bell', Bell)} ${this.toolbarButton('Command palette', 'command', Command)} <button type="button" aria-label=${this.isDark ? 'Switch to light theme' : 'Switch to dark theme'} class="text-muted-foreground hover:bg-sidebar-accent hover:text-foreground flex aspect-square size-8 items-center justify-center rounded-md transition-colors" @click=${() => { this.isDark = !this.isDark setDark(this.isDark) }} > ${this.isDark ? icon(Sun, 'sun', 'size-4') : icon(Moon, 'moon', 'size-4')} </button> <uip-sidebar-trigger class="text-muted-foreground hover:bg-sidebar-accent hover:text-foreground size-8 rounded-md"></uip-sidebar-trigger> </div> ` : html` <div class="flex items-center justify-center pb-1"> <uip-sidebar-trigger class="text-muted-foreground hover:bg-sidebar-accent hover:text-foreground size-8 rounded-md"></uip-sidebar-trigger> </div> `} </uip-sidebar-footer> <uip-sidebar-rail></uip-sidebar-rail> </uip-sidebar> ` } } customElements.get('uip-sidebar-07') || customElements.define('uip-sidebar-07', UipSidebar07) declare global { interface HTMLElementTagNameMap { 'uip-sidebar-07': UipSidebar07 } }
Raw manifest:https://uipkge.dev/r/lit/sidebar-07.json