{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "data-product-card",
  "title": "Data Product Card",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-angular/blocks/data-product-card/data-product-card.component.ts",
      "content": "import { ChangeDetectionStrategy, Component, Input, OnDestroy, signal } from '@angular/core'\nimport {\n  Activity,\n  Award,\n  Check,\n  ChevronRight,\n  Copy,\n  Database,\n  HardDrive,\n  KeyRound,\n  Layers,\n  LucideAngularModule,\n  Radio,\n  Server,\n  ShieldCheck,\n  Users,\n  Zap,\n} from 'lucide-angular'\nimport { cn } from '@/lib/utils'\nimport { UiAvatarComponent, UiAvatarFallbackComponent } from '@/ui/avatar/avatar.component'\nimport { UiBadgeComponent } from '@/ui/badge/badge.component'\nimport { UiButtonComponent } from '@/ui/button/button.component'\nimport {\n  UiCardComponent,\n  UiCardContentComponent,\n  UiCardDescriptionComponent,\n  UiCardFooterComponent,\n  UiCardHeaderComponent,\n} from '@/ui/card/card.component'\nimport { UiProgressComponent } from '@/ui/progress/progress.component'\nimport { UiSeparatorComponent } from '@/ui/separator/separator.component'\n\nexport interface OutputPort {\n  id: string\n  name: string\n  type: 'snowflake' | 'kafka' | 'graphql' | 's3' | 'postgres'\n  typeLabel: string\n  uri: string\n  latency: string\n  status: 'online' | 'degraded' | 'syncing'\n}\n\nexport interface DataProductData {\n  id: string\n  name: string\n  version: string\n  domain: string\n  tier: 'Gold Tier 1' | 'Silver Tier 2' | 'Bronze Tier 3'\n  description: string\n  slaCompliance: number\n  freshness: string\n  completenessPct: number\n  activeConsumersCount: number\n  owner: {\n    name: string\n    role: string\n    team: string\n    avatar?: string\n    fallback: string\n  }\n  outputPorts: OutputPort[]\n  tags: string[]\n  certifiedCompliance: string\n  lastUpdated: string\n}\n\nexport const DEFAULT_DATA_PRODUCT: DataProductData = {\n  id: 'dp-cust-360',\n  name: 'dp_customer_360_profile',\n  version: 'v3.2.0',\n  domain: 'Customer Intelligence & Growth',\n  tier: 'Gold Tier 1',\n  description:\n    'Unified customer identity graph consolidating CRM accounts, Stripe transaction volumes, and behavioral product telemetry into clean semantic marts.',\n  slaCompliance: 99.8,\n  freshness: 'Updated 8m ago · Hourly sync',\n  completenessPct: 99.4,\n  activeConsumersCount: 14,\n  owner: {\n    name: 'Marcus Vance',\n    role: 'Staff Data Architect',\n    team: 'Data Platform Squad',\n    fallback: 'MV',\n  },\n  outputPorts: [\n    {\n      id: 'port-snow',\n      name: 'analytics.gold_customer_360',\n      type: 'snowflake',\n      typeLabel: 'Snowflake Table',\n      uri: 'snowflake://analytics_prod.dw/analytics.gold_customer_360',\n      latency: '< 1h Batch SLA',\n      status: 'online',\n    },\n    {\n      id: 'port-kafka',\n      name: 'events.customer.updates.v2',\n      type: 'kafka',\n      typeLabel: 'Kafka Stream',\n      uri: 'kafka://events.kafka.internal:9092/events.customer.updates.v2',\n      latency: '< 100ms Event Time',\n      status: 'online',\n    },\n    {\n      id: 'port-gql',\n      name: 'GraphQL Query Port',\n      type: 'graphql',\n      typeLabel: 'GraphQL API',\n      uri: 'https://api.uipkge.dev/graphql?query=customer360',\n      latency: '< 45ms p99',\n      status: 'online',\n    },\n    {\n      id: 'port-s3',\n      name: 'Parquet Lake Export',\n      type: 's3',\n      typeLabel: 'Parquet S3 Lake',\n      uri: 's3://lakehouse-gold/customer_360/snapshot_latest.parquet',\n      latency: '24h Partitioned',\n      status: 'online',\n    },\n  ],\n  tags: ['#customer360', '#revenue', '#identity', '#gold_marts', '#dbt_certified'],\n  certifiedCompliance: 'SOC2 Type II & GDPR Art. 15 Compliant',\n  lastUpdated: 'Aug 21, 2026',\n}\n\n/**\n * DataProductCard -- the React block, 1:1: a Data Mesh data product card with the\n * governance ribbon, telemetry strip, selectable output ports, a URI inspector with\n * copy, the owner/tags bar and Query Port / Request Access actions.\n */\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-data-product-card, [ui-data-product-card]',\n  standalone: true,\n  host: { class: 'block' },\n  imports: [\n    LucideAngularModule,\n    UiAvatarComponent,\n    UiAvatarFallbackComponent,\n    UiBadgeComponent,\n    UiButtonComponent,\n    UiCardComponent,\n    UiCardHeaderComponent,\n    UiCardDescriptionComponent,\n    UiCardContentComponent,\n    UiCardFooterComponent,\n    UiProgressComponent,\n    UiSeparatorComponent,\n  ],\n  template: `\n    <div data-slot=\"data-product-card\" [class]=\"cn('w-full', className)\">\n      <div\n        ui-card\n        class=\"border-border bg-card text-card-foreground hover:border-primary/40 relative flex flex-col overflow-hidden shadow-xs transition-colors duration-200 hover:shadow-sm\"\n      >\n        <div ui-card-header class=\"space-y-3 p-5 pb-3\">\n          <!-- Badges & Governance Ribbon -->\n          <div class=\"flex flex-wrap items-center justify-between gap-2\">\n            <div class=\"flex flex-wrap items-center gap-1.5\">\n              <!-- Domain Tag -->\n              <span\n                ui-badge\n                wrap\n                variant=\"outline\"\n                class=\"border-border bg-muted/30 text-muted-foreground gap-1 text-xs font-normal\"\n              >\n                <lucide-icon [img]=\"Layers\" class=\"text-primary size-3\" aria-hidden=\"true\" />\n                <span>{{ product.domain }}</span>\n              </span>\n\n              <!-- Tier Tag -->\n              <span\n                ui-badge\n                wrap\n                variant=\"secondary\"\n                class=\"border-success/30 bg-success/10 text-success font-mono text-xs font-semibold\"\n              >\n                <lucide-icon [img]=\"Award\" class=\"size-3\" aria-hidden=\"true\" />\n                {{ product.tier }}\n              </span>\n            </div>\n\n            <!-- Compliance Seal -->\n            <div class=\"text-muted-foreground flex items-center gap-1 text-xs\">\n              <lucide-icon [img]=\"ShieldCheck\" class=\"text-success size-3.5\" aria-hidden=\"true\" />\n              <span class=\"hidden font-medium sm:inline\">{{ product.certifiedCompliance }}</span>\n              <span class=\"font-medium sm:hidden\">SOC2 Certified</span>\n            </div>\n          </div>\n\n          <!-- Title & Version Lockup -->\n          <div class=\"space-y-1\">\n            <div class=\"flex flex-wrap items-center justify-between gap-2\">\n              <div class=\"flex items-baseline gap-2\">\n                <h3 class=\"text-foreground font-mono text-base font-bold tracking-tight break-all sm:text-lg\">\n                  {{ product.name }}\n                </h3>\n                <span class=\"text-muted-foreground font-mono text-xs font-medium\">{{ product.version }}</span>\n              </div>\n\n              <span ui-badge wrap variant=\"outline\" class=\"text-muted-foreground gap-1 text-xs font-normal\">\n                <lucide-icon [img]=\"Activity\" class=\"text-success size-3 animate-pulse\" aria-hidden=\"true\" />\n                <span class=\"font-mono tabular-nums\">{{ product.slaCompliance }}% SLA</span>\n              </span>\n            </div>\n\n            <!-- Business Value Description -->\n            <p ui-card-description class=\"text-muted-foreground text-xs leading-relaxed sm:text-sm\">\n              {{ product.description }}\n            </p>\n          </div>\n        </div>\n\n        <div ui-card-content class=\"flex-1 space-y-4 p-5 pt-0\">\n          <!-- 4 Key Telemetry Metrics Strip -->\n          <div\n            class=\"border-border/80 bg-muted/20 grid grid-cols-2 gap-2 rounded-lg border p-2.5 text-xs sm:grid-cols-4 sm:p-3\"\n          >\n            <div class=\"space-y-0.5\">\n              <span class=\"text-muted-foreground block\">SLA Compliance</span>\n              <div class=\"flex items-center gap-1.5\">\n                <span class=\"text-success text-success font-mono text-sm font-bold tabular-nums\">\n                  {{ product.slaCompliance }}%\n                </span>\n                <span class=\"text-success font-semibold\">✓</span>\n              </div>\n              <ui-progress [value]=\"product.slaCompliance\" class=\"bg-muted h-1.5\" />\n            </div>\n\n            <div class=\"space-y-0.5\">\n              <span class=\"text-muted-foreground block\">Data Freshness</span>\n              <span class=\"text-foreground block truncate font-mono text-xs font-semibold\" [title]=\"product.freshness\">\n                {{ product.freshness }}\n              </span>\n              <span class=\"text-muted-foreground text-xs\">SLA: &lt; 1h Target</span>\n            </div>\n\n            <div class=\"space-y-0.5\">\n              <span class=\"text-muted-foreground block\">Completeness</span>\n              <span class=\"text-foreground block font-mono text-sm font-bold tabular-nums\">\n                {{ product.completenessPct }}%\n              </span>\n              <span class=\"text-muted-foreground text-xs\">0.6% Max Nulls</span>\n            </div>\n\n            <div class=\"space-y-0.5\">\n              <span class=\"text-muted-foreground block\">Active Consumers</span>\n              <div class=\"flex items-center gap-1\">\n                <lucide-icon [img]=\"Users\" class=\"text-primary size-3.5\" aria-hidden=\"true\" />\n                <span class=\"text-foreground font-mono text-sm font-bold tabular-nums\">\n                  {{ product.activeConsumersCount }} Apps\n                </span>\n              </div>\n              <span class=\"text-muted-foreground text-xs\">Certified Live Ports</span>\n            </div>\n          </div>\n\n          <!-- Multi-Modal Output Ports Selector -->\n          <div class=\"space-y-2\">\n            <div class=\"flex items-center justify-between\">\n              <span class=\"text-foreground text-xs font-semibold tracking-wide uppercase\">\n                Certified Multi-Modal Output Ports ({{ product.outputPorts.length }})\n              </span>\n              <span class=\"text-muted-foreground text-xs\">Click port to inspect URI</span>\n            </div>\n\n            <div class=\"grid grid-cols-1 gap-2 sm:grid-cols-2\">\n              @for (port of product.outputPorts; track port.id) {\n                <button\n                  type=\"button\"\n                  [class]=\"portClass(port)\"\n                  [attr.aria-pressed]=\"isSelected(port)\"\n                  (click)=\"selectedPortId.set(port.id)\"\n                >\n                  <div class=\"flex min-w-0 items-start gap-2.5\">\n                    <div\n                      class=\"bg-muted text-foreground mt-0.5 flex size-7 shrink-0 items-center justify-center rounded-md\"\n                    >\n                      @switch (port.type) {\n                        @case ('snowflake') {\n                          <lucide-icon [img]=\"Database\" class=\"text-info size-3.5\" aria-hidden=\"true\" />\n                        }\n                        @case ('kafka') {\n                          <lucide-icon [img]=\"Radio\" class=\"text-warning size-3.5\" aria-hidden=\"true\" />\n                        }\n                        @case ('graphql') {\n                          <lucide-icon [img]=\"Server\" class=\"text-chart-1 size-3.5\" aria-hidden=\"true\" />\n                        }\n                        @default {\n                          <lucide-icon [img]=\"HardDrive\" class=\"text-success size-3.5\" aria-hidden=\"true\" />\n                        }\n                      }\n                    </div>\n                    <div class=\"min-w-0 space-y-0.5\">\n                      <div class=\"flex items-center gap-1.5\">\n                        <span class=\"text-foreground truncate text-xs font-semibold\">{{ port.typeLabel }}</span>\n                        <span class=\"bg-success size-1.5 shrink-0 rounded-full\" aria-hidden=\"true\"></span>\n                      </div>\n                      <p class=\"text-muted-foreground truncate font-mono text-xs\">{{ port.name }}</p>\n                      <p class=\"text-muted-foreground text-xs font-normal\">{{ port.latency }}</p>\n                    </div>\n                  </div>\n\n                  <div class=\"shrink-0 pt-0.5 pl-1\">\n                    @if (isSelected(port)) {\n                      <span\n                        class=\"bg-primary text-primary-foreground flex size-4 items-center justify-center rounded-full text-xs font-bold\"\n                      >\n                        ✓\n                      </span>\n                    } @else {\n                      <lucide-icon\n                        [img]=\"ChevronRight\"\n                        class=\"text-muted-foreground/40 group-hover:text-foreground size-4\"\n                        aria-hidden=\"true\"\n                      />\n                    }\n                  </div>\n                </button>\n              }\n            </div>\n          </div>\n\n          <!-- Active Selected Port URI Inspector Box -->\n          <div\n            class=\"border-border bg-muted/40 flex flex-col gap-2 rounded-lg border p-3 text-xs sm:flex-row sm:items-center sm:justify-between\"\n          >\n            <div class=\"min-w-0 space-y-0.5\">\n              <span class=\"text-muted-foreground text-xs font-medium\">Selected Endpoint URI:</span>\n              <p class=\"text-foreground truncate font-mono text-xs font-medium select-all\">{{ selectedPort()?.uri }}</p>\n            </div>\n\n            <div class=\"flex shrink-0 items-center gap-1.5 pt-1 sm:pt-0\">\n              <button\n                ui-button\n                variant=\"outline\"\n                size=\"sm\"\n                class=\"h-7 gap-1 px-2 text-xs transition-transform duration-150 active:scale-95\"\n                (click)=\"copySelected()\"\n              >\n                @if (isCopied()) {\n                  <lucide-icon [img]=\"Check\" class=\"text-success size-3\" aria-hidden=\"true\" />\n                } @else {\n                  <lucide-icon [img]=\"Copy\" class=\"size-3\" aria-hidden=\"true\" />\n                }\n                <span>{{ isCopied() ? 'Copied' : 'Copy URI' }}</span>\n              </button>\n            </div>\n          </div>\n\n          <!-- Product Owner & Tags Bar -->\n          <div class=\"flex flex-wrap items-center justify-between gap-3 pt-1\">\n            <!-- Owner Profile -->\n            <div class=\"flex items-center gap-2\">\n              <span ui-avatar class=\"border-border size-7 border\">\n                <span ui-avatar-fallback class=\"bg-primary/10 text-primary text-xs font-semibold\">\n                  {{ product.owner.fallback }}\n                </span>\n              </span>\n              <div>\n                <p class=\"text-foreground text-xs leading-none font-medium\">{{ product.owner.name }}</p>\n                <p class=\"text-muted-foreground text-xs\">{{ product.owner.team }}</p>\n              </div>\n            </div>\n\n            <!-- Tags -->\n            <div class=\"flex flex-wrap items-center gap-1\">\n              @for (tag of product.tags.slice(0, 3); track tag) {\n                <span\n                  ui-badge\n                  wrap\n                  variant=\"outline\"\n                  class=\"text-muted-foreground border-border/70 text-xs font-normal\"\n                >\n                  {{ tag }}\n                </span>\n              }\n            </div>\n          </div>\n        </div>\n\n        <ui-separator />\n\n        <!-- Footer Action Controls -->\n        <div ui-card-footer class=\"flex flex-wrap items-center justify-between gap-2 p-4 sm:px-5\">\n          <div class=\"text-muted-foreground text-xs\">\n            Last contract verified: <span class=\"text-foreground font-medium\">{{ product.lastUpdated }}</span>\n          </div>\n\n          <div class=\"flex items-center gap-2\">\n            <button\n              ui-button\n              variant=\"outline\"\n              size=\"sm\"\n              class=\"h-8 gap-1.5 text-xs font-medium active:scale-[0.98]\"\n              [disabled]=\"isQueryingPort()\"\n              (click)=\"handleQueryPort()\"\n            >\n              @if (!isQueryingPort()) {\n                <lucide-icon [img]=\"Zap\" class=\"text-warning size-3.5\" aria-hidden=\"true\" />\n              }\n              @if (isQueryingPort()) {\n                <span>Running Test Query...</span>\n              } @else {\n                <span>Query Port</span>\n              }\n            </button>\n\n            <button\n              ui-button\n              size=\"sm\"\n              class=\"h-8 gap-1.5 text-xs font-semibold shadow-xs active:scale-[0.98]\"\n              [disabled]=\"accessRequested()\"\n              (click)=\"handleRequestAccess()\"\n            >\n              <lucide-icon [img]=\"KeyRound\" class=\"size-3.5\" aria-hidden=\"true\" />\n              <span>{{ accessRequested() ? 'Access Requested ✓' : 'Request Access' }}</span>\n            </button>\n          </div>\n        </div>\n      </div>\n    </div>\n  `,\n})\nexport class UiDataProductCardComponent implements OnDestroy {\n  @Input() product: DataProductData = DEFAULT_DATA_PRODUCT\n  @Input() variant: 'full' | 'compact' | 'minimal' = 'full'\n  @Input('class') className?: string\n\n  protected readonly cn = cn\n  protected readonly Activity = Activity\n  protected readonly Award = Award\n  protected readonly Check = Check\n  protected readonly ChevronRight = ChevronRight\n  protected readonly Copy = Copy\n  protected readonly Database = Database\n  protected readonly HardDrive = HardDrive\n  protected readonly KeyRound = KeyRound\n  protected readonly Layers = Layers\n  protected readonly Radio = Radio\n  protected readonly Server = Server\n  protected readonly ShieldCheck = ShieldCheck\n  protected readonly Users = Users\n  protected readonly Zap = Zap\n\n  readonly copiedPortId = signal<string | null>(null)\n  readonly accessRequested = signal(false)\n  readonly isQueryingPort = signal(false)\n  /** null until the user picks a port: React seeds it from the first port (or 'port-snow'). */\n  readonly selectedPortId = signal<string | null>(null)\n\n  private readonly timers = new Set<ReturnType<typeof setTimeout>>()\n\n  private currentPortId(): string {\n    return this.selectedPortId() ?? this.product.outputPorts[0]?.id ?? 'port-snow'\n  }\n\n  selectedPort(): OutputPort | undefined {\n    const id = this.currentPortId()\n    return this.product.outputPorts.find((p) => p.id === id) ?? this.product.outputPorts[0]\n  }\n\n  isSelected(port: OutputPort): boolean {\n    return this.currentPortId() === port.id\n  }\n\n  isCopied(): boolean {\n    const port = this.selectedPort()\n    return this.copiedPortId() === port?.id\n  }\n\n  portClass(port: OutputPort): string {\n    return cn(\n      'group flex items-start justify-between rounded-lg border p-2.5 text-left transition-[colors,transform] duration-150 active:scale-[0.98]',\n      this.isSelected(port)\n        ? 'border-primary/50 bg-primary/5 ring-primary/30 ring-1'\n        : 'border-border bg-card hover:border-primary/25 hover:bg-muted/40',\n    )\n  }\n\n  private later(fn: () => void, ms: number): void {\n    const t = setTimeout(() => {\n      this.timers.delete(t)\n      fn()\n    }, ms)\n    this.timers.add(t)\n  }\n\n  copyUri(port: OutputPort): void {\n    if (typeof navigator !== 'undefined' && navigator.clipboard) {\n      void navigator.clipboard.writeText(port.uri)\n      this.copiedPortId.set(port.id)\n      this.later(() => this.copiedPortId.set(null), 2000)\n    }\n  }\n\n  copySelected(): void {\n    const port = this.selectedPort()\n    if (port) this.copyUri(port)\n  }\n\n  handleRequestAccess(): void {\n    this.accessRequested.set(true)\n    this.later(() => this.accessRequested.set(false), 4000)\n  }\n\n  handleQueryPort(): void {\n    this.isQueryingPort.set(true)\n    this.later(() => this.isQueryingPort.set(false), 1200)\n  }\n\n  ngOnDestroy(): void {\n    for (const t of this.timers) clearTimeout(t)\n    this.timers.clear()\n  }\n}\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/data-product-card/data-product-card.component.ts"
    },
    {
      "path": "packages/registry-angular/blocks/data-product-card/index.ts",
      "content": "export {\n  UiDataProductCardComponent,\n  DEFAULT_DATA_PRODUCT,\n  type DataProductData,\n  type OutputPort,\n} from './data-product-card.component'\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/data-product-card/index.ts"
    }
  ],
  "dependencies": [
    "lucide-angular"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/angular/avatar.json",
    "https://uipkge.dev/r/angular/badge.json",
    "https://uipkge.dev/r/angular/button.json",
    "https://uipkge.dev/r/angular/card.json",
    "https://uipkge.dev/r/angular/progress.json",
    "https://uipkge.dev/r/angular/separator.json"
  ],
  "description": "Data Mesh architectural Data Product Card (DPC) featuring SLA compliance health, multi-modal output ports (Snowflake, Kafka, GraphQL, Parquet S3), domain ownership, consumer counts, and access request triggers.",
  "categories": [
    "devops",
    "app",
    "data",
    "analytics"
  ]
}