Starred Project Sidebar
blocklayoutColorful, customised app sidebar -- branded project tiles with star favorites, badge counts, team avatars with status dots, and a user pill footer. Inline search input with ⌘F shortcut. Modeled on a HRMS workspace shell.
Installation
$pnpm dlx shadcn@latest add https://uipkge.dev/r/lit/sidebar-06.json$npx shadcn@latest add https://uipkge.dev/r/lit/sidebar-06.json$yarn dlx shadcn@latest add https://uipkge.dev/r/lit/sidebar-06.json$bunx shadcn@latest add https://uipkge.dev/r/lit/sidebar-06.jsonManifest:
https://uipkge.dev/r/lit/sidebar-06.jsonInstalls to:src/uip/blocks/sidebar-06/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-06': UipSidebar06
}
}Files installed (1)
src/uip/blocks/sidebar-06/sidebar-06.ts16 kB
import { LitElement, css, html } from 'lit' import { Briefcase, Calendar, ChartLine, ChevronsUpDown, ClipboardList, Folder, GraduationCap, HeartPulse, HelpCircle, Inbox, LayoutDashboard, Monitor, Moon, Palette, Search, Send, Settings as SettingsIcon, Star, 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/dropdown-menu/dropdown-menu' import '../../components/input/input' import type { UipInput } from '../../components/input/input' import '../../components/sidebar/sidebar' import type { UipSidebarProvider } from '../../components/sidebar/sidebar' type ThemeValue = 'light' | 'dark' | 'system' function getTheme(): ThemeValue { try { const stored = localStorage.getItem('uipkge-theme') if (stored === 'light' || stored === 'dark' || stored === 'system') return stored } catch { // ignore } 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) } // Colorful, customised HRMS-style sidebar -- branded project tiles, badges, // status dots, and a user pill footer. Every row is spelled out inline so // you can swap colors and labels in place. /** * <uip-sidebar-06> — the registry Sidebar06 block as a web component. * * Class strings are React's verbatim. The footer dropdown's `side` follows * the provider's isMobile (React's useSidebar); the theme submenu writes * the `dark` class on documentElement directly (the `next-themes` * dependency is not ported — same visual result, persisted to * localStorage). The search field is <uip-input> with `prefix`/`suffix` * slots (React's SidebarInput prefix/suffix props). No props, no events: * edit the markup in place. */ export class UipSidebar06 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 }, query: { state: true }, } private theme: ThemeValue = 'light' private query = '' private media?: MediaQueryList constructor() { super() new ThemeController(this) } connectedCallback() { super.connectedCallback() this.setAttribute('data-uipkge', '') this.setAttribute('data-slot', 'sidebar-06') this.theme = getTheme() // Re-render when the viewport crosses the mobile breakpoint so the // footer dropdown side follows isMobile (React's useSidebar re-render). this.media = window.matchMedia('(max-width: 768px)') this.media.addEventListener('change', this.rerender) } disconnectedCallback() { super.disconnectedCallback() this.media?.removeEventListener('change', this.rerender) } private rerender = () => this.requestUpdate() render() { const isMobile = (this.closest('uip-sidebar-provider') as UipSidebarProvider | null)?.isMobile ?? false 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 shadow-sm 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> <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>F </kbd> </uip-input> </div> </uip-sidebar-header> <uip-sidebar-content> <uip-sidebar-group> <uip-sidebar-group-content> <uip-sidebar-menu> <uip-sidebar-menu-item> <uip-sidebar-menu-button is-active tooltip="Dashboard"> ${icon(LayoutDashboard, 'layout-dashboard', 'size-4')} <span>Dashboard</span> </uip-sidebar-menu-button> </uip-sidebar-menu-item> <uip-sidebar-menu-item> <uip-sidebar-menu-button tooltip="Employees"> ${icon(Users, 'users', 'size-4')} <span>Employees</span> </uip-sidebar-menu-button> </uip-sidebar-menu-item> <uip-sidebar-menu-item> <uip-sidebar-menu-button tooltip="Clients"> ${icon(Briefcase, 'briefcase', 'size-4')} <span>Clients</span> </uip-sidebar-menu-button> </uip-sidebar-menu-item> <uip-sidebar-menu-item> <uip-sidebar-menu-button tooltip="Projects"> ${icon(Folder, 'folder', 'size-4')} <span>Projects</span> </uip-sidebar-menu-button> </uip-sidebar-menu-item> </uip-sidebar-menu> </uip-sidebar-group-content> </uip-sidebar-group> <uip-sidebar-separator></uip-sidebar-separator> <uip-sidebar-group> <uip-sidebar-group-label>Workforce</uip-sidebar-group-label> <uip-sidebar-group-content> <uip-sidebar-menu> <uip-sidebar-menu-item> <uip-sidebar-menu-button tooltip="Calendar"> ${icon(Calendar, 'calendar', 'size-4')} <span>Calendar</span> </uip-sidebar-menu-button> </uip-sidebar-menu-item> <uip-sidebar-menu-item> <uip-sidebar-menu-button tooltip="Attendance"> ${icon(ClipboardList, 'clipboard-list', 'size-4')} <span>Attendance</span> </uip-sidebar-menu-button> </uip-sidebar-menu-item> <uip-sidebar-menu-item> <uip-sidebar-menu-button tooltip="Inbox"> ${icon(Inbox, 'inbox', 'size-4')} <span>Inbox</span> </uip-sidebar-menu-button> <uip-sidebar-menu-badge class="[&::part(base)]:bg-destructive [&::part(base)]:text-white">6</uip-sidebar-menu-badge> </uip-sidebar-menu-item> <uip-sidebar-menu-item> <uip-sidebar-menu-button tooltip="Analytics"> ${icon(ChartLine, 'chart-line', 'size-4')} <span>Analytics</span> </uip-sidebar-menu-button> </uip-sidebar-menu-item> </uip-sidebar-menu> </uip-sidebar-group-content> </uip-sidebar-group> <uip-sidebar-separator></uip-sidebar-separator> <uip-sidebar-group> <uip-sidebar-group-label>Projects</uip-sidebar-group-label> <uip-sidebar-group-content> <uip-sidebar-menu> ${[ { tile: 'bg-chart-1', icon: Folder, iconName: 'folder', name: 'Shift Planner' }, { tile: 'bg-chart-2', icon: GraduationCap, iconName: 'graduation-cap', name: 'Training Portal' }, { tile: 'bg-chart-3', icon: HeartPulse, iconName: 'heart-pulse', name: 'Performance Hub' }, { tile: 'bg-chart-4', icon: ChartLine, iconName: 'chart-line', name: 'Expense Claims' }, ].map( (p) => html` <uip-sidebar-menu-item> <uip-sidebar-menu-button tooltip=${p.name}> <div class=${`${p.tile} flex aspect-square size-5 shrink-0 items-center justify-center rounded-md text-white`}> ${icon(p.icon, p.iconName, 'size-3')} </div> <span>${p.name}</span> </uip-sidebar-menu-button> <uip-sidebar-menu-badge class="[&::part(base)]:text-warning"> ${icon(Star, 'star', 'size-3.5 fill-current')} </uip-sidebar-menu-badge> </uip-sidebar-menu-item> `, )} </uip-sidebar-menu> </uip-sidebar-group-content> </uip-sidebar-group> <uip-sidebar-separator></uip-sidebar-separator> <uip-sidebar-group> <uip-sidebar-group-label>My team</uip-sidebar-group-label> <uip-sidebar-group-content> <uip-sidebar-menu> ${[ { initials: 'OB', tile: 'bg-chart-1/15', name: 'Olivia Bennett', dot: 'bg-destructive' }, { initials: 'DM', tile: 'bg-chart-2/15', name: 'Daniel Morgan', dot: 'bg-warning' }, { initials: 'ER', tile: 'bg-chart-3/15', name: 'Ethan Reynolds', dot: 'bg-success' }, ].map( (person) => html` <uip-sidebar-menu-item> <uip-sidebar-menu-button tooltip=${person.name}> <uip-avatar class="size-5"> <uip-avatar-fallback class=${`${person.tile} text-foreground text-xs`}>${person.initials}</uip-avatar-fallback> </uip-avatar> <span>${person.name}</span> </uip-sidebar-menu-button> <uip-sidebar-menu-badge> <span class=${`${person.dot} size-1.5 rounded-full`}></span> </uip-sidebar-menu-badge> </uip-sidebar-menu-item> `, )} </uip-sidebar-menu> </uip-sidebar-group-content> </uip-sidebar-group> <uip-sidebar-separator></uip-sidebar-separator> <uip-sidebar-group> <uip-sidebar-group-label>Administration</uip-sidebar-group-label> <uip-sidebar-group-content> <uip-sidebar-menu> <uip-sidebar-menu-item> <uip-sidebar-menu-button tooltip="Settings"> ${icon(SettingsIcon, 'settings', 'size-4')} <span>Settings</span> </uip-sidebar-menu-button> </uip-sidebar-menu-item> <uip-sidebar-menu-item> <uip-sidebar-menu-button tooltip="Help center"> ${icon(HelpCircle, 'circle-question-mark', 'size-4')} <span>Help center</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" > <div class="relative shrink-0"> <uip-avatar class="h-8 w-8 rounded-lg group-data-[collapsible=icon]:size-6"> <uip-avatar-fallback class="bg-sidebar-accent text-sidebar-accent-foreground rounded-lg text-xs">JC</uip-avatar-fallback> </uip-avatar> <span class="bg-destructive ring-sidebar absolute -right-0.5 -bottom-0.5 size-2.5 rounded-full ring-2"></span> </div> <div class="grid flex-1 text-left text-sm leading-tight group-data-[collapsible=icon]:hidden"> <span class="truncate font-medium">James Carter</span> <span class="text-muted-foreground truncate text-xs">Admin</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=${isMobile ? 'bottom' : 'right'} align="end" side-offset="4"> <uip-dropdown-menu-label>James Carter</uip-dropdown-menu-label> <uip-dropdown-menu-separator></uip-dropdown-menu-separator> <uip-dropdown-menu-item>${icon(Send, 'send', 'size-4')} Invite teammates</uip-dropdown-menu-item> <uip-dropdown-menu-item>${icon(SettingsIcon, 'settings', 'size-4')} Workspace settings</uip-dropdown-menu-item> <uip-dropdown-menu-separator></uip-dropdown-menu-separator> <uip-dropdown-menu-sub> <uip-dropdown-menu-sub-trigger> ${icon(Palette, 'palette', 'size-4')} 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', 'size-4')} Light</uip-dropdown-menu-radio-item> <uip-dropdown-menu-radio-item value="dark">${icon(Moon, 'moon', 'size-4')} Dark</uip-dropdown-menu-radio-item> <uip-dropdown-menu-radio-item value="system">${icon(Monitor, 'monitor', 'size-4')} System</uip-dropdown-menu-radio-item> </uip-dropdown-menu-radio-group> </uip-dropdown-menu-sub-content> </uip-dropdown-menu-sub> </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-06') || customElements.define('uip-sidebar-06', UipSidebar06) declare global { interface HTMLElementTagNameMap { 'uip-sidebar-06': UipSidebar06 } }
Raw manifest:https://uipkge.dev/r/lit/sidebar-06.json