Menu

Change language

GitHub repo

User Profile Page

blockauth

Profile 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

$npx shadcn@latest add https://uipkge.dev/r/lit/user-profile-page.json
Manifest:https://uipkge.dev/r/lit/user-profile-page.jsonInstalls to:src/uip/blocks/user-profile-page/

Variants

Loading interactive previews…

Schema

Type aliases exported from this item's source. Use these to shape the data you pass in.

HTMLElementTagNameMap
interface 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">[email protected]</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