{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "profile-menu",
  "title": "Profile Menu",
  "type": "registry:block",
  "description": "Header-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.",
  "categories": [
    "layout",
    "dashboard",
    "overlay"
  ],
  "files": [
    {
      "path": "packages/elements-lit/src/blocks/profile-menu/profile-menu.ts",
      "content": "import { LitElement, css, html } from 'lit'\nimport { BadgeCheck, Bell, CreditCard, LogOut, Settings, Zap } from 'lucide'\nimport { tailwind } from '../../lib/styles'\nimport { ThemeController } from '../../lib/theme'\nimport { icon } from '../../lib/icon'\n// Side-effect imports: register the composed elements for standalone use\n// (consumers who install only this block still get working primitives).\nimport '../../components/avatar/avatar'\nimport '../../components/dropdown-menu/dropdown-menu'\n\nexport type ProfileMenuKey = 'upgrade' | 'account' | 'billing' | 'notifications' | 'settings' | 'logout'\n\n/**\n * <uip-profile-menu> — the registry ProfileMenu block as a web component.\n *\n * Class strings are React's ProfileMenu verbatim. React's `trigger` render\n * prop becomes a `trigger` slot (default avatar button when empty);\n * `onSelect` becomes a `select` CustomEvent (bubbles, composed, detail = the\n * item key). `user` is split into `user-name` / `user-email` / `user-avatar`\n * attributes (`user` alone can't cross the HTML boundary).\n */\nexport class UipProfileMenu extends LitElement {\n  static shadowRootOptions = { ...LitElement.shadowRootOptions, delegatesFocus: true }\n  // :host display can't be a utility class (the host has no template of its own).\n  static styles = [tailwind, css`:host { display: inline-block; }`]\n\n  static properties = {\n    userName: { attribute: 'user-name' },\n    userEmail: { attribute: 'user-email' },\n    userAvatar: { attribute: 'user-avatar' },\n  }\n\n  userName = 'Alex Morgan'\n  userEmail = 'alex@example.com'\n  userAvatar = ''\n\n  constructor() {\n    super()\n    new ThemeController(this)\n  }\n\n  connectedCallback() {\n    super.connectedCallback()\n    this.setAttribute('data-uipkge', '')\n    this.setAttribute('data-slot', 'profile-menu')\n  }\n\n  private get initials() {\n    return (\n      this.userName\n        .trim()\n        .split(/\\s+/)\n        .slice(0, 2)\n        .map((p) => p[0]?.toUpperCase())\n        .join('') || 'U'\n    )\n  }\n\n  private select(key: ProfileMenuKey) {\n    this.dispatchEvent(new CustomEvent('select', { detail: key, bubbles: true, composed: true }))\n  }\n\n  render() {\n    return html`\n      <uip-dropdown-menu\n        class=\"[&::part(content)]:w-60 [&::part(content)]:rounded-lg [&::part(user-label)]:p-0 [&::part(user-label)]:font-normal\"\n      >\n        <slot name=\"trigger\" slot=\"trigger\">\n          <button\n            type=\"button\"\n            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\"\n            aria-label=\"Open profile menu\"\n          >\n            <uip-avatar class=\"[&::part(base)]:size-7\">\n              ${this.userAvatar\n                ? html`<uip-avatar-image src=${this.userAvatar} alt=${this.userName}></uip-avatar-image>`\n                : ''}\n              <uip-avatar-fallback class=\"text-xs font-semibold [&::part(base)]:bg-primary/10 [&::part(base)]:text-primary\"\n                >${this.initials}</uip-avatar-fallback\n              >\n            </uip-avatar>\n          </button>\n        </slot>\n        <uip-dropdown-menu-content class=\"w-60 rounded-lg\" align=\"end\" side-offset=\"8\">\n          <uip-dropdown-menu-label part=\"user-label\">\n            <div class=\"flex items-center gap-2 px-1 py-1.5 text-left text-sm\">\n              <uip-avatar class=\"[&::part(base)]:size-9 [&::part(base)]:rounded-lg\">\n                ${this.userAvatar\n                  ? html`<uip-avatar-image src=${this.userAvatar} alt=${this.userName}></uip-avatar-image>`\n                  : ''}\n                <uip-avatar-fallback\n                  class=\"text-xs font-semibold [&::part(base)]:rounded-lg [&::part(base)]:bg-primary/10 [&::part(base)]:text-primary\"\n                >\n                  ${this.initials}\n                </uip-avatar-fallback>\n              </uip-avatar>\n              <div class=\"grid flex-1 text-left leading-tight\">\n                <span class=\"truncate text-sm font-semibold\">${this.userName}</span>\n                <span class=\"text-muted-foreground truncate text-xs\">${this.userEmail}</span>\n              </div>\n            </div>\n          </uip-dropdown-menu-label>\n          <uip-dropdown-menu-separator></uip-dropdown-menu-separator>\n          <uip-dropdown-menu-group>\n            <uip-dropdown-menu-item @select=${() => this.select('upgrade')}>\n              ${icon(Zap, 'zap', 'size-4')} Upgrade to Pro\n            </uip-dropdown-menu-item>\n          </uip-dropdown-menu-group>\n          <uip-dropdown-menu-separator></uip-dropdown-menu-separator>\n          <uip-dropdown-menu-group>\n            <uip-dropdown-menu-item @select=${() => this.select('account')}>\n              ${icon(BadgeCheck, 'badge-check', 'size-4')} Account\n            </uip-dropdown-menu-item>\n            <uip-dropdown-menu-item @select=${() => this.select('billing')}>\n              ${icon(CreditCard, 'credit-card', 'size-4')} Billing\n            </uip-dropdown-menu-item>\n            <uip-dropdown-menu-item @select=${() => this.select('notifications')}>\n              ${icon(Bell, 'bell', 'size-4')} Notifications\n            </uip-dropdown-menu-item>\n            <uip-dropdown-menu-item @select=${() => this.select('settings')}>\n              ${icon(Settings, 'settings', 'size-4')} Settings\n            </uip-dropdown-menu-item>\n          </uip-dropdown-menu-group>\n          <uip-dropdown-menu-separator></uip-dropdown-menu-separator>\n          <uip-dropdown-menu-item @select=${() => this.select('logout')}>\n            ${icon(LogOut, 'log-out', 'size-4')} Log out\n          </uip-dropdown-menu-item>\n        </uip-dropdown-menu-content>\n      </uip-dropdown-menu>\n    `\n  }\n}\n\ncustomElements.get('uip-profile-menu') || customElements.define('uip-profile-menu', UipProfileMenu)\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'uip-profile-menu': UipProfileMenu\n  }\n}\n",
      "type": "registry:file",
      "target": "src/uip/blocks/profile-menu/profile-menu.ts"
    }
  ],
  "dependencies": [
    "lit",
    "lucide"
  ],
  "registryDependencies": [
    "https://uipkge.dev/r/lit/avatar.json",
    "https://uipkge.dev/r/lit/dropdown-menu.json",
    "https://uipkge.dev/r/lit/lit-core.json"
  ]
}
