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 uipkge-ng@latest add user-profile-page
Manifest:https://uipkge.dev/r/angular/user-profile-page.jsonInstalls to:src/app/components/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.

DetailRow
interface DetailRow {
  icon: LucideIconData
  label: string
  value: string
}
ActivityItem
interface ActivityItem {
  icon: LucideIconData
  sentence: string
  time: string
}
ProjectCard
interface ProjectCard {
  name: string
  description: string
  progress: number
}

Files installed (2)

  • src/app/components/blocks/user-profile-page/user-profile-page.component.ts9.1 kB
    // 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). Port of the React block 1:1.
    import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
    import {
      CalendarDays,
      FilePlus2,
      GitCommitHorizontal,
      Link,
      LucideAngularModule,
      Mail,
      MapPin,
      MessageSquare,
      MoreHorizontal,
      Star,
      type LucideIconData,
    } from 'lucide-angular'
    import { cn } from '@/lib/utils'
    import { UiAvatarComponent, UiAvatarFallbackComponent, UiAvatarImageComponent } from '@/ui/avatar/avatar.component'
    import { UiBadgeComponent } from '@/ui/badge/badge.component'
    import { UiButtonComponent } from '@/ui/button/button.component'
    import {
      UiCardComponent,
      UiCardContentComponent,
      UiCardDescriptionComponent,
      UiCardHeaderComponent,
      UiCardTitleComponent,
    } from '@/ui/card/card.component'
    import {
      UiDropdownMenuComponent,
      UiDropdownMenuContentComponent,
      UiDropdownMenuItemComponent,
      UiDropdownMenuSeparatorComponent,
      UiDropdownMenuTriggerComponent,
    } from '@/ui/dropdown-menu/dropdown-menu.component'
    import { UiProgressComponent } from '@/ui/progress/progress.component'
    import { UiSeparatorComponent } from '@/ui/separator/separator.component'
    import {
      UiTabsComponent,
      UiTabsContentComponent,
      UiTabsListComponent,
      UiTabsTriggerComponent,
    } from '@/ui/tabs/tabs.component'
    
    interface DetailRow {
      icon: LucideIconData
      label: string
      value: string
    }
    
    interface ActivityItem {
      icon: LucideIconData
      sentence: string
      time: string
    }
    
    interface ProjectCard {
      name: string
      description: string
      progress: number
    }
    
    const details: DetailRow[] = [
      { icon: Mail, label: 'Email', value: 'amara@acme.com' },
      { icon: MapPin, label: 'Location', value: 'Lisbon, Portugal' },
      { icon: CalendarDays, label: 'Joined', value: 'March 2023' },
      { icon: Link, label: 'Website', value: 'amaraosei.dev' },
    ]
    
    const activity: ActivityItem[] = [
      { icon: GitCommitHorizontal, sentence: 'Pushed 6 commits to design-tokens', time: '2h ago' },
      { icon: MessageSquare, sentence: 'Commented on "Sidebar collapse behavior"', time: '5h ago' },
      { icon: FilePlus2, sentence: 'Created project Marketing site refresh', time: 'Yesterday' },
      { icon: Star, sentence: 'Starred repository uipkge-ui', time: '3d ago' },
    ]
    
    const projects: ProjectCard[] = [
      { name: 'Design tokens', description: 'Shared OKLCH palette and theme bindings.', progress: 92 },
      { name: 'Marketing site', description: 'Astro rebuild of the public landing pages.', progress: 64 },
      { name: 'Component audit', description: 'Parity pass across Vue and React mirrors.', progress: 41 },
      { name: 'Docs search', description: 'Client-side fuzzy search over registry items.', progress: 18 },
    ]
    
    @Component({
      changeDetection: ChangeDetectionStrategy.Eager,
      selector: 'ui-user-profile-page, [ui-user-profile-page]',
      standalone: true,
      // React renders the root <div> itself: the host stays out of layout and `class` goes to the root.
      host: { '[attr.class]': '"contents"' },
      imports: [
        LucideAngularModule,
        UiAvatarComponent,
        UiAvatarFallbackComponent,
        UiAvatarImageComponent,
        UiBadgeComponent,
        UiButtonComponent,
        UiCardComponent,
        UiCardContentComponent,
        UiCardDescriptionComponent,
        UiCardHeaderComponent,
        UiCardTitleComponent,
        UiDropdownMenuComponent,
        UiDropdownMenuContentComponent,
        UiDropdownMenuItemComponent,
        UiDropdownMenuSeparatorComponent,
        UiDropdownMenuTriggerComponent,
        UiProgressComponent,
        UiSeparatorComponent,
        UiTabsComponent,
        UiTabsContentComponent,
        UiTabsListComponent,
        UiTabsTriggerComponent,
      ],
      template: `
        <div data-slot="user-profile-page" [class]="rootClass">
          <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">
              <ui-avatar size="2xl" class="size-20">
                <ui-avatar-image src="https://i.pravatar.cc/160?img=47" alt="Amara Osei" />
                <ui-avatar-fallback>AO</ui-avatar-fallback>
              </ui-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>
                  <span ui-badge variant="secondary">Design engineer</span>
                </div>
                <p class="text-muted-foreground mt-1 text-sm">&#64;amara</p>
              </div>
              <div class="flex items-center gap-2">
                <button ui-button variant="outline" size="sm">Edit profile</button>
                <ui-dropdown-menu>
                  <button ui-button ui-dropdown-menu-trigger variant="ghost" size="icon" aria-label="More actions">
                    <lucide-icon [img]="MoreHorizontal" class="size-4" />
                  </button>
                  <ui-dropdown-menu-content align="end">
                    <ui-dropdown-menu-item>Copy profile link</ui-dropdown-menu-item>
                    <ui-dropdown-menu-item>Share…</ui-dropdown-menu-item>
                    <ui-dropdown-menu-separator />
                    <ui-dropdown-menu-item variant="destructive">Block user</ui-dropdown-menu-item>
                  </ui-dropdown-menu-content>
                </ui-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>
    
            <ui-tabs defaultValue="overview" class="mt-6 gap-4">
              <ui-tabs-list>
                <button ui-tabs-trigger value="overview">Overview</button>
                <button ui-tabs-trigger value="activity">Activity</button>
                <button ui-tabs-trigger value="projects">Projects</button>
              </ui-tabs-list>
    
              <ui-tabs-content 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>
                <ui-separator />
                <dl class="grid gap-x-8 gap-y-4 sm:grid-cols-2">
                  @for (detail of details; track detail.label) {
                    <div class="flex items-center gap-3">
                      <lucide-icon [img]="detail.icon" class="text-muted-foreground size-4 shrink-0" />
                      <dt class="text-muted-foreground text-sm">{{ detail.label }}</dt>
                      <dd class="ml-auto text-sm font-medium">{{ detail.value }}</dd>
                    </div>
                  }
                </dl>
              </ui-tabs-content>
    
              <ui-tabs-content value="activity" class="pt-2">
                <ul class="divide-y">
                  @for (item of activity; track item.sentence) {
                    <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"
                      >
                        <lucide-icon [img]="item.icon" class="size-4" />
                      </span>
                      <p class="min-w-0 flex-1 truncate text-sm">{{ item.sentence }}</p>
                      <time class="text-muted-foreground text-xs whitespace-nowrap">{{ item.time }}</time>
                    </li>
                  }
                </ul>
              </ui-tabs-content>
    
              <ui-tabs-content value="projects" class="pt-2">
                <div class="grid gap-4 sm:grid-cols-2">
                  @for (project of projects; track project.name) {
                    <div ui-card>
                      <div ui-card-header>
                        <h3 ui-card-title class="text-sm font-medium">{{ project.name }}</h3>
                        <p ui-card-description class="truncate text-sm">{{ project.description }}</p>
                      </div>
                      <div ui-card-content class="flex items-center gap-3">
                        <ui-progress [value]="project.progress" class="flex-1" />
                        <span class="text-muted-foreground text-xs tabular-nums">{{ project.progress }}%</span>
                      </div>
                    </div>
                  }
                </div>
              </ui-tabs-content>
            </ui-tabs>
          </div>
        </div>
      `,
    })
    export class UiUserProfilePageComponent {
      protected readonly MoreHorizontal = MoreHorizontal
      protected readonly details = details
      protected readonly activity = activity
      protected readonly projects = projects
    
      @Input('class') className?: string
    
      get rootClass(): string {
        return cn('bg-background border-border overflow-hidden rounded-xl border shadow-xs', this.className)
      }
    }
    
  • src/app/components/blocks/user-profile-page/index.ts0.1 kB

Raw manifest:https://uipkge.dev/r/angular/user-profile-page.json