Documentation Sidebar
blocklayoutDocs-style sidebar with a version switcher at the top, a search input below, and collapsible navigation groups. All sample data inlined so consumers edit routes in place.
Installation
$pnpm dlx shadcn@latest add https://uipkge.dev/r/lit/sidebar-03.json$npx shadcn@latest add https://uipkge.dev/r/lit/sidebar-03.json$yarn dlx shadcn@latest add https://uipkge.dev/r/lit/sidebar-03.json$bunx shadcn@latest add https://uipkge.dev/r/lit/sidebar-03.jsonManifest:
https://uipkge.dev/r/lit/sidebar-03.jsonInstalls to:src/uip/blocks/sidebar-03/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.
NavChildinterface NavChild {
title: string
url: string
isActive?: boolean
}NavIteminterface NavItem {
title: string
url: string
items: NavChild[]
}HTMLElementTagNameMapinterface HTMLElementTagNameMap {
'uip-sidebar-03': UipSidebar03
}
}Files installed (1)
src/uip/blocks/sidebar-03/sidebar-03.ts8.2 kB
import { LitElement, css, html } from 'lit' import { Check, ChevronRight, ChevronsUpDown, Search } 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/dropdown-menu/dropdown-menu' import '../../components/collapsible/collapsible' import '../../components/input/input' import type { UipInput } from '../../components/input/input' import '../../components/label/label' import '../../components/sidebar/sidebar' // Sample data (React's `data`, inlined so consumers edit routes in place). const versions = ['1.0.1', '1.1.0-alpha', '2.0.0-beta1'] interface NavChild { title: string url: string isActive?: boolean } interface NavItem { title: string url: string items: NavChild[] } const navMain: NavItem[] = [ { title: 'Getting Started', url: '#', items: [ { title: 'Installation', url: '#' }, { title: 'Project Structure', url: '#' }, ], }, { title: 'Building Your Application', url: '#', items: [ { title: 'Routing', url: '#' }, { title: 'Data Fetching', url: '#', isActive: true }, { title: 'Rendering', url: '#' }, { title: 'Caching', url: '#' }, { title: 'Styling', url: '#' }, { title: 'Optimizing', url: '#' }, { title: 'Configuring', url: '#' }, { title: 'Testing', url: '#' }, { title: 'Authentication', url: '#' }, { title: 'Deploying', url: '#' }, { title: 'Upgrading', url: '#' }, { title: 'Examples', url: '#' }, ], }, { title: 'API Reference', url: '#', items: [ { title: 'Components', url: '#' }, { title: 'File Conventions', url: '#' }, { title: 'Functions', url: '#' }, { title: 'next.config.js Options', url: '#' }, { title: 'CLI', url: '#' }, { title: 'Edge Runtime', url: '#' }, ], }, { title: 'Architecture', url: '#', items: [ { title: 'Accessibility', url: '#' }, { title: 'Fast Refresh', url: '#' }, { title: 'Next.js Compiler', url: '#' }, { title: 'Supported Browsers', url: '#' }, { title: 'Turbopack', url: '#' }, ], }, { title: 'Community', url: '#', items: [{ title: 'Contribution Guide', url: '#' }], }, ] /** * <uip-sidebar-03> — the registry Sidebar03 block as a web component * (VersionSwitcher + SearchForm inlined — one block, one install unit). * * Class strings are React's verbatim. The version switcher keeps its * selected version in { state: true }; the search field reads like any * input. No props, no events: edit the markup in place. */ export class UipSidebar03 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 = { selectedVersion: { state: true }, search: { state: true }, } private selectedVersion = versions[0]! private search = '' constructor() { super() new ThemeController(this) } connectedCallback() { super.connectedCallback() this.setAttribute('data-uipkge', '') this.setAttribute('data-slot', 'sidebar-03') } render() { return html` <uip-sidebar> <uip-sidebar-header> <uip-sidebar-menu> <uip-sidebar-menu-item> <uip-dropdown-menu class="[&::part(content)]:min-w-56"> <uip-sidebar-menu-button slot="trigger" size="lg" class="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground" > <div class="bg-sidebar-primary text-sidebar-primary-foreground flex aspect-square size-8 items-center justify-center rounded-lg"> <svg viewBox="0 0 24 24" class="size-4" 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="flex flex-col gap-0.5 leading-none"> <span class="font-medium">uipkge</span> <span>v${this.selectedVersion}</span> </div> ${icon(ChevronsUpDown, 'chevrons-up-down', 'ml-auto')} </uip-sidebar-menu-button> <uip-dropdown-menu-content align="start"> ${versions.map( (version) => html` <uip-dropdown-menu-item @select=${() => { this.selectedVersion = version }} > v${version} ${this.selectedVersion === version ? icon(Check, 'check', 'ml-auto') : ''} </uip-dropdown-menu-item> `, )} </uip-dropdown-menu-content> </uip-dropdown-menu> </uip-sidebar-menu-item> </uip-sidebar-menu> <form @submit=${(e: Event) => e.preventDefault()}> <uip-sidebar-group class="[&::part(base)]:py-0"> <uip-sidebar-group-content> <uip-label for="search" class="sr-only">Search</uip-label> <uip-input id="search" type="text" placeholder="Search the docs..." allow-clear class="[&::part(control)]:bg-background [&::part(control)]:h-8 [&::part(control)]:shadow-none" .value=${this.search} @input=${(e: Event) => { this.search = (e.target as UipInput).value }} > <span slot="prefix">${icon(Search, 'search', 'size-3.5')}</span> </uip-input> </uip-sidebar-group-content> </uip-sidebar-group> </form> </uip-sidebar-header> <uip-sidebar-content class="[&::part(base)]:gap-0"> ${navMain.map( (item) => html` <uip-collapsible class="contents group/collapsible" default-open> <uip-sidebar-group> <uip-collapsible-trigger as-child> <uip-sidebar-group-label class="group/label [&::part(base)]:text-sidebar-foreground [&::part(base)]:text-sm [&::part(base)]:hover:bg-sidebar-accent [&::part(base)]:hover:text-sidebar-accent-foreground" > ${item.title} ${icon(ChevronRight, 'chevron-right', 'ml-auto transition-transform group-data-[state=open]/collapsible:rotate-90')} </uip-sidebar-group-label> </uip-collapsible-trigger> <uip-collapsible-content> <uip-sidebar-group-content> <uip-sidebar-menu> ${item.items.map( (childItem) => html` <uip-sidebar-menu-item> <uip-sidebar-menu-button href=${item.url} ?is-active=${!!childItem.isActive}> ${childItem.title} </uip-sidebar-menu-button> </uip-sidebar-menu-item> `, )} </uip-sidebar-menu> </uip-sidebar-group-content> </uip-collapsible-content> </uip-sidebar-group> </uip-collapsible> `, )} </uip-sidebar-content> <uip-sidebar-rail></uip-sidebar-rail> </uip-sidebar> ` } } customElements.get('uip-sidebar-03') || customElements.define('uip-sidebar-03', UipSidebar03) declare global { interface HTMLElementTagNameMap { 'uip-sidebar-03': UipSidebar03 } }
Raw manifest:https://uipkge.dev/r/lit/sidebar-03.json