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 uipkge-ng@latest add user-profile-page$npx uipkge-ng@latest add user-profile-page$yarn dlx uipkge-ng@latest add user-profile-page$bunx uipkge-ng@latest add user-profile-pageManifest:
https://uipkge.dev/r/angular/user-profile-page.jsonInstalls to:src/app/components/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.
DetailRowinterface DetailRow {
icon: LucideIconData
label: string
value: string
}ActivityIteminterface ActivityItem {
icon: LucideIconData
sentence: string
time: string
}ProjectCardinterface ProjectCard {
name: string
description: string
progress: number
}npm dependencies
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: '[email protected]' }, { 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">@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
export { UiUserProfilePageComponent } from './user-profile-page.component'
Raw manifest:https://uipkge.dev/r/angular/user-profile-page.json