Profile Menu
blocklayoutHeader-grade profile/account dropdown. Slot-trigger DropdownMenu the consumer wraps around their own avatar button (default trigger ships an Avatar fallback). Sections: upgrade, account/billing/notifications/settings, log out. Emits `select` with the chosen item key so consumers wire navigation and auth themselves.
Installation
$pnpm dlx uipkge-ng@latest add profile-menu$npx uipkge-ng@latest add profile-menu$yarn dlx uipkge-ng@latest add profile-menu$bunx uipkge-ng@latest add profile-menuManifest:
https://uipkge.dev/r/angular/profile-menu.jsonInstalls to:src/app/components/blocks/profile-menu/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.
ProfileMenuUserinterface ProfileMenuUser {
name: string
email: string
avatar?: string
}npm dependencies
Includes
Files installed (2)
src/app/components/blocks/profile-menu/profile-menu.component.ts5.6 kB
// Header-grade profile/account dropdown: default Avatar trigger (consumers can project their // own button), sections for upgrade / account / billing / notifications / settings / log out. // Emits `select` with the chosen item key. Port of the Vue block 1:1. import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output, computed, signal } from '@angular/core' import { BadgeCheck, Bell, CreditCard, LogOut, LucideAngularModule, Settings, Zap } from 'lucide-angular' import { UiAvatarFallbackComponent, UiAvatarImageComponent, UiAvatarComponent } from '@/ui/avatar/avatar.component' import { UiDropdownMenuComponent, UiDropdownMenuContentComponent, UiDropdownMenuGroupComponent, UiDropdownMenuItemComponent, UiDropdownMenuLabelComponent, UiDropdownMenuSeparatorComponent, UiDropdownMenuTriggerComponent, } from '@/ui/dropdown-menu/dropdown-menu.component' export interface ProfileMenuUser { name: string email: string avatar?: string } export type ProfileMenuKey = 'upgrade' | 'account' | 'billing' | 'notifications' | 'settings' | 'logout' @Component({ changeDetection: ChangeDetectionStrategy.Eager, selector: 'ui-profile-menu, [ui-profile-menu]', standalone: true, // Vue renders the DropdownMenu root itself: the host stays out of layout. host: { '[attr.class]': '"contents"' }, imports: [ LucideAngularModule, UiAvatarComponent, UiAvatarFallbackComponent, UiAvatarImageComponent, UiDropdownMenuComponent, UiDropdownMenuContentComponent, UiDropdownMenuGroupComponent, UiDropdownMenuItemComponent, UiDropdownMenuLabelComponent, UiDropdownMenuSeparatorComponent, UiDropdownMenuTriggerComponent, ], template: ` <ui-dropdown-menu data-slot="profile-menu" [open]="open()" (openChange)="open.set($event)"> <button ui-dropdown-menu-trigger type="button" class="hover:bg-accent focus-visible:ring-ring flex size-8 items-center justify-center rounded-lg transition-colors focus-visible:ring-1 focus-visible:outline-none" aria-label="Open profile menu" > <ng-content> <ui-avatar class="size-7"> @if (user().avatar) { <ui-avatar-image [src]="user().avatar!" [alt]="user().name" /> } <ui-avatar-fallback class="bg-primary/10 text-primary text-xs font-semibold"> {{ initials() }} </ui-avatar-fallback> </ui-avatar> </ng-content> </button> <ui-dropdown-menu-content class="w-60 rounded-lg" align="end" [sideOffset]="8"> <ui-dropdown-menu-label class="p-0 font-normal"> <div class="flex items-center gap-2 px-1 py-1.5 text-left text-sm"> <ui-avatar class="size-9 rounded-lg"> @if (user().avatar) { <ui-avatar-image [src]="user().avatar!" [alt]="user().name" /> } <ui-avatar-fallback class="bg-primary/10 text-primary rounded-lg text-xs font-semibold"> {{ initials() }} </ui-avatar-fallback> </ui-avatar> <div class="grid flex-1 text-left leading-tight"> <span class="truncate text-sm font-semibold">{{ user().name }}</span> <span class="text-muted-foreground truncate text-xs">{{ user().email }}</span> </div> </div> </ui-dropdown-menu-label> <ui-dropdown-menu-separator /> <ui-dropdown-menu-group> <ui-dropdown-menu-item (select)="handleSelect('upgrade')"> <lucide-icon [img]="Zap" class="size-4" /> Upgrade to Pro </ui-dropdown-menu-item> </ui-dropdown-menu-group> <ui-dropdown-menu-separator /> <ui-dropdown-menu-group> <ui-dropdown-menu-item (select)="handleSelect('account')"> <lucide-icon [img]="BadgeCheck" class="size-4" /> Account </ui-dropdown-menu-item> <ui-dropdown-menu-item (select)="handleSelect('billing')"> <lucide-icon [img]="CreditCard" class="size-4" /> Billing </ui-dropdown-menu-item> <ui-dropdown-menu-item (select)="handleSelect('notifications')"> <lucide-icon [img]="Bell" class="size-4" /> Notifications </ui-dropdown-menu-item> <ui-dropdown-menu-item (select)="handleSelect('settings')"> <lucide-icon [img]="Settings" class="size-4" /> Settings </ui-dropdown-menu-item> </ui-dropdown-menu-group> <ui-dropdown-menu-separator /> <ui-dropdown-menu-item (select)="handleSelect('logout')"> <lucide-icon [img]="LogOut" class="size-4" /> Log out </ui-dropdown-menu-item> </ui-dropdown-menu-content> </ui-dropdown-menu> `, }) export class UiProfileMenuComponent { protected readonly BadgeCheck = BadgeCheck protected readonly Bell = Bell protected readonly CreditCard = CreditCard protected readonly LogOut = LogOut protected readonly Settings = Settings protected readonly Zap = Zap private readonly _user = signal<ProfileMenuUser>({ name: 'Alex Morgan', email: 'alex@example.com', avatar: '' }) readonly user = this._user.asReadonly() @Input('user') set userInput(v: ProfileMenuUser | undefined) { if (v) this._user.set(v) } @Output() select = new EventEmitter<ProfileMenuKey>() readonly open = signal(false) readonly initials = computed(() => { const parts = this._user().name.trim().split(/\s+/).slice(0, 2) return parts.map((p) => p[0]?.toUpperCase()).join('') || 'U' }) handleSelect(key: ProfileMenuKey): void { this.select.emit(key) } }src/app/components/blocks/profile-menu/index.ts0.1 kB
export { UiProfileMenuComponent, type ProfileMenuKey, type ProfileMenuUser } from './profile-menu.component'
Raw manifest:https://uipkge.dev/r/angular/profile-menu.json