User Profile Page
blockauthProfile page composition: gradient cover banner with an overlapping ringed avatar, identity row with role badge plus edit button and a kebab actions menu, an inline follower stats row, and tabs for Overview (bio + icon detail rows), Activity (muted-tile feed), and Projects (progress cards). All data is hardcoded inline for you to replace.
Installation
$pnpm dlx shadcn@latest add https://uipkge.dev/r/lit/user-profile-page.json$npx shadcn@latest add https://uipkge.dev/r/lit/user-profile-page.json$yarn dlx shadcn@latest add https://uipkge.dev/r/lit/user-profile-page.json$bunx shadcn@latest add https://uipkge.dev/r/lit/user-profile-page.jsonManifest:
https://uipkge.dev/r/lit/user-profile-page.jsonInstalls to:src/uip/blocks/user-profile-page/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-user-profile-page': UipUserProfilePage
}
}Files installed (1)
src/uip/blocks/user-profile-page/user-profile-page.ts11.5 kB
import { LitElement, css, html } from 'lit' import { CalendarDays, FilePlus2, GitCommitHorizontal, Link2, Mail, MapPin, MessageSquare, MoreHorizontal, Star, } from 'lucide' import { cn } from '../../lib/utils' 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/badge/badge' import '../../components/button/button' import '../../components/card/card' import '../../components/dropdown-menu/dropdown-menu' import '../../components/progress/progress' import '../../components/separator/separator' import '../../components/tabs/tabs' /** * <uip-user-profile-page> — the registry UserProfilePage block as a web component. * * Class strings are React's UserProfilePage verbatim. All data is hardcoded * inline as in React (replace after install). Tabs use the same * slot=trigger/content + data-value pairing as login-01. The kebab menu is a * <uip-dropdown-menu> with slot="trigger"; its items are inert in the demo, * matching React (no handlers). */ export class UipUserProfilePage 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 = { tab: { state: true }, } private tab = 'overview' constructor() { super() new ThemeController(this) } connectedCallback() { super.connectedCallback() this.setAttribute('data-uipkge', '') this.setAttribute('data-slot', 'user-profile-page') } render() { return html` <div data-slot="user-profile-page" class=${cn('bg-background border-border overflow-hidden rounded-xl border shadow-xs')} > <div class="from-primary/20 via-primary/5 h-32 bg-gradient-to-br to-transparent" aria-hidden="true"></div> <div class="px-6 pb-6"> <div class="ring-background -mt-10 ml-6 flex size-20 items-center justify-center rounded-full ring-4"> <uip-avatar size="2xl" class="size-20"> <uip-avatar-image src="https://i.pravatar.cc/160?img=47" alt="Amara Osei"></uip-avatar-image> <uip-avatar-fallback>AO</uip-avatar-fallback> </uip-avatar> </div> <div class="mt-4 flex flex-wrap items-start justify-between gap-4"> <div> <div class="flex flex-wrap items-center gap-2"> <h1 class="text-xl font-semibold tracking-tight">Amara Osei</h1> <uip-badge variant="secondary">Design engineer</uip-badge> </div> <p class="text-muted-foreground mt-1 text-sm">@amara</p> </div> <div class="flex items-center gap-2"> <uip-button variant="outline" size="sm">Edit profile</uip-button> <uip-dropdown-menu> <uip-button slot="trigger" variant="ghost" size="icon" aria-label="More actions"> ${icon(MoreHorizontal, 'ellipsis', 'size-4')} </uip-button> <uip-dropdown-menu-content align="end"> <uip-dropdown-menu-item>Copy profile link</uip-dropdown-menu-item> <uip-dropdown-menu-item>Share…</uip-dropdown-menu-item> <uip-dropdown-menu-separator></uip-dropdown-menu-separator> <uip-dropdown-menu-item part="destructive">Block user</uip-dropdown-menu-item> </uip-dropdown-menu-content> </uip-dropdown-menu> </div> </div> <p class="mt-4 text-sm"> <span class="font-semibold">24</span> <span class="text-muted-foreground">Projects</span> <span class="font-semibold">1,248</span> <span class="text-muted-foreground">Followers</span> <span class="font-semibold">380</span> <span class="text-muted-foreground">Following</span> </p> <uip-tabs class="mt-6 gap-4" value=${this.tab} @value-change=${(e: CustomEvent<{ value: string }>) => { this.tab = e.detail.value }} > <span slot="trigger" data-value="overview">Overview</span> <span slot="trigger" data-value="activity">Activity</span> <span slot="trigger" data-value="projects">Projects</span> <div slot="content" data-value="overview" class="space-y-6 pt-2"> <p class="max-w-prose text-sm leading-relaxed"> Design engineer focused on token systems and component architecture. Previously at a fintech startup, now building the shared UI language for Acme. Writes about OKLCH color and registry-driven workflows. </p> <uip-separator></uip-separator> <dl class="grid gap-x-8 gap-y-4 sm:grid-cols-2"> <div class="flex items-center gap-3"> ${icon(Mail, 'mail', 'text-muted-foreground size-4 shrink-0')} <dt class="text-muted-foreground text-sm">Email</dt> <dd class="ml-auto text-sm font-medium">amara@acme.com</dd> </div> <div class="flex items-center gap-3"> ${icon(MapPin, 'map-pin', 'text-muted-foreground size-4 shrink-0')} <dt class="text-muted-foreground text-sm">Location</dt> <dd class="ml-auto text-sm font-medium">Lisbon, Portugal</dd> </div> <div class="flex items-center gap-3"> ${icon(CalendarDays, 'calendar-days', 'text-muted-foreground size-4 shrink-0')} <dt class="text-muted-foreground text-sm">Joined</dt> <dd class="ml-auto text-sm font-medium">March 2023</dd> </div> <div class="flex items-center gap-3"> ${icon(Link2, 'link', 'text-muted-foreground size-4 shrink-0')} <dt class="text-muted-foreground text-sm">Website</dt> <dd class="ml-auto text-sm font-medium">amaraosei.dev</dd> </div> </dl> </div> <div slot="content" data-value="activity" class="pt-2"> <ul class="divide-y"> <li class="flex items-center gap-3 py-3 first:pt-0 last:pb-0"> <span class="bg-muted text-muted-foreground flex size-8 shrink-0 items-center justify-center rounded-md"> ${icon(GitCommitHorizontal, 'git-commit-horizontal', 'size-4')} </span> <p class="min-w-0 flex-1 truncate text-sm">Pushed 6 commits to design-tokens</p> <time class="text-muted-foreground text-xs whitespace-nowrap">2h ago</time> </li> <li class="flex items-center gap-3 py-3 first:pt-0 last:pb-0"> <span class="bg-muted text-muted-foreground flex size-8 shrink-0 items-center justify-center rounded-md"> ${icon(MessageSquare, 'message-square', 'size-4')} </span> <p class="min-w-0 flex-1 truncate text-sm">Commented on "Sidebar collapse behavior"</p> <time class="text-muted-foreground text-xs whitespace-nowrap">5h ago</time> </li> <li class="flex items-center gap-3 py-3 first:pt-0 last:pb-0"> <span class="bg-muted text-muted-foreground flex size-8 shrink-0 items-center justify-center rounded-md"> ${icon(FilePlus2, 'file-plus-2', 'size-4')} </span> <p class="min-w-0 flex-1 truncate text-sm">Created project Marketing site refresh</p> <time class="text-muted-foreground text-xs whitespace-nowrap">Yesterday</time> </li> <li class="flex items-center gap-3 py-3 first:pt-0 last:pb-0"> <span class="bg-muted text-muted-foreground flex size-8 shrink-0 items-center justify-center rounded-md"> ${icon(Star, 'star', 'size-4')} </span> <p class="min-w-0 flex-1 truncate text-sm">Starred repository uipkge-ui</p> <time class="text-muted-foreground text-xs whitespace-nowrap">3d ago</time> </li> </ul> </div> <div slot="content" data-value="projects" class="pt-2"> <div class="grid gap-4 sm:grid-cols-2"> <uip-card> <uip-card-header> <uip-card-title class="text-sm font-medium">Design tokens</uip-card-title> <uip-card-description class="truncate text-sm" >Shared OKLCH palette and theme bindings.</uip-card-description > </uip-card-header> <uip-card-content class="flex items-center gap-3"> <uip-progress value="92" class="flex-1"></uip-progress> <span class="text-muted-foreground text-xs tabular-nums">92%</span> </uip-card-content> </uip-card> <uip-card> <uip-card-header> <uip-card-title class="text-sm font-medium">Marketing site</uip-card-title> <uip-card-description class="truncate text-sm" >Astro rebuild of the public landing pages.</uip-card-description > </uip-card-header> <uip-card-content class="flex items-center gap-3"> <uip-progress value="64" class="flex-1"></uip-progress> <span class="text-muted-foreground text-xs tabular-nums">64%</span> </uip-card-content> </uip-card> <uip-card> <uip-card-header> <uip-card-title class="text-sm font-medium">Component audit</uip-card-title> <uip-card-description class="truncate text-sm" >Parity pass across Vue and React mirrors.</uip-card-description > </uip-card-header> <uip-card-content class="flex items-center gap-3"> <uip-progress value="41" class="flex-1"></uip-progress> <span class="text-muted-foreground text-xs tabular-nums">41%</span> </uip-card-content> </uip-card> <uip-card> <uip-card-header> <uip-card-title class="text-sm font-medium">Docs search</uip-card-title> <uip-card-description class="truncate text-sm" >Client-side fuzzy search over registry items.</uip-card-description > </uip-card-header> <uip-card-content class="flex items-center gap-3"> <uip-progress value="18" class="flex-1"></uip-progress> <span class="text-muted-foreground text-xs tabular-nums">18%</span> </uip-card-content> </uip-card> </div> </div> </uip-tabs> </div> </div> ` } } customElements.get('uip-user-profile-page') || customElements.define('uip-user-profile-page', UipUserProfilePage) declare global { interface HTMLElementTagNameMap { 'uip-user-profile-page': UipUserProfilePage } }
Raw manifest:https://uipkge.dev/r/lit/user-profile-page.json