Menu

Change language

GitHub repo

Customer Metrics Band

blockmarketing

Customer velocity and ROI telemetry band with cohort segmentation (Enterprise vs High-Growth), 4 verified KPI metric tiles, and deep-dive impact inspector.

Installation

$npx uipkge-ng@latest add customer-metrics-band
Manifest:https://uipkge.dev/r/angular/customer-metrics-band.jsonInstalls to:src/app/components/blocks/customer-metrics-band/

Variants

Loading interactive previews…

Schema

Type aliases exported from this item's source. Use these to shape the data you pass in.

MetricStat
interface MetricStat {
  id: string
  label: string
  value: string
  baseline: string
  change: string
  isPositive: boolean
  description: string
  details: {
    before: string
    after: string
    impact: string
  }
}

npm dependencies

Includes

Files installed (2)

  • src/app/components/blocks/customer-metrics-band/customer-metrics-band.component.ts13.3 kB
    // Customer velocity and ROI telemetry band with cohort segmentation (Enterprise vs
    // High-Growth), 4 verified KPI metric tiles, and a deep-dive impact inspector.
    // Port of the React block 1:1.
    import { ChangeDetectionStrategy, Component, Input, computed, signal } from '@angular/core'
    import { Gauge, LucideAngularModule } from 'lucide-angular'
    import { cn } from '@/lib/utils'
    import { UiBadgeComponent } from '@/ui/badge/badge.component'
    import { UiCardComponent } from '@/ui/card/card.component'
    
    export type CustomerCohort = 'all' | 'enterprise' | 'startups'
    
    export interface MetricStat {
      id: string
      label: string
      value: string
      baseline: string
      change: string
      isPositive: boolean
      description: string
      details: {
        before: string
        after: string
        impact: string
      }
    }
    
    const metricsByCohort: Record<CustomerCohort, MetricStat[]> = {
      all: [
        {
          id: 'velocity',
          label: 'Time to First Production Release',
          value: '4.2x',
          baseline: 'Reduced from 14 weeks to 18 days',
          change: '+320% Faster',
          isPositive: true,
          description: 'Engineering squads ship complete vertical workflows without custom CSS boilerplate.',
          details: { before: '98 Days average', after: '18 Days average', impact: '80 days saved per major launch' },
        },
        {
          id: 'vitals',
          label: 'Core Web Vitals Pass Rate',
          value: '99.4%',
          baseline: 'Tested across 10M+ mobile pageviews',
          change: '+28.4% vs monolithic',
          isPositive: true,
          description: 'OKLCH zero-runtime styling ensures sub-50ms INP and pristine Lighthouse 100 scores.',
          details: { before: '71.0% Pass Rate', after: '99.4% Pass Rate', impact: 'Zero layout shift (CLS 0.00)' },
        },
        {
          id: 'maintenance',
          label: 'Design System Maintenance Cost',
          value: '-74%',
          baseline: 'Engineering hours spent upgrading packages',
          change: '-74% Reduction',
          isPositive: true,
          description: 'Direct code ownership eliminates breaking semver dependency conflicts permanently.',
          details: { before: '24 hrs / developer / mo', after: '6.2 hrs / developer / mo', impact: '17.8 hrs reclaimed' },
        },
        {
          id: 'cloud_savings',
          label: 'Average Annual Compute & Bandwidth Saved',
          value: '$184k',
          baseline: 'Calculated across high-traffic SaaS suites',
          change: 'Zero AST bloat',
          isPositive: true,
          description: 'Zero unused component CSS in bundle output lowers edge bandwidth distribution costs.',
          details: { before: '640 kB vendor chunks', after: '38 kB tree-shaken chunks', impact: '94% payload reduction' },
        },
      ],
      enterprise: [
        {
          id: 'velocity',
          label: 'Time to First Production Release',
          value: '5.8x',
          baseline: 'Enterprise multi-app design alignment',
          change: '+480% Faster',
          isPositive: true,
          description: 'Standardized AST across 20+ squads without package coordination overhead.',
          details: { before: '180 Days average', after: '31 Days average', impact: '149 days saved per project' },
        },
        {
          id: 'vitals',
          label: 'Core Web Vitals Pass Rate',
          value: '99.8%',
          baseline: 'Internal enterprise portals & dashboards',
          change: '+34.2% uplift',
          isPositive: true,
          description: 'Strict keyboard ergonomics and WCAG AA accessibility out of the box.',
          details: { before: '65.6% Pass Rate', after: '99.8% Pass Rate', impact: '100% compliance audit pass' },
        },
        {
          id: 'maintenance',
          label: 'Design System Maintenance Cost',
          value: '-82%',
          baseline: 'Eliminated dedicated registry sync team',
          change: '-82% Saved',
          isPositive: true,
          description: 'Engineers customize source code in their own repo without waiting on upstream merges.',
          details: {
            before: '3 FTEs maintenance',
            after: '0.5 FTE maintenance',
            impact: '2.5 FTEs reallocated to product',
          },
        },
        {
          id: 'cloud_savings',
          label: 'Average Annual Compute & Bandwidth Saved',
          value: '$420k',
          baseline: 'Tier-1 enterprise infrastructure',
          change: 'Zero CVE risk',
          isPositive: true,
          description: 'No third-party runtime dependencies eliminates transitive package auditing costs.',
          details: { before: '$650k vendor licensing', after: '$230k infrastructure', impact: '$420k saved annually' },
        },
      ],
      startups: [
        {
          id: 'velocity',
          label: 'Time to First Production Release',
          value: '6.4x',
          baseline: 'From idea to paying users in days',
          change: '+540% Faster',
          isPositive: true,
          description: 'Plug-and-play workbenches and CRM blocks ready for Stripe integration on day 1.',
          details: { before: '45 Days to MVP', after: '7 Days to MVP', impact: '38 days faster to revenue' },
        },
        {
          id: 'vitals',
          label: 'Core Web Vitals Pass Rate',
          value: '99.1%',
          baseline: 'Landing pages and conversion flows',
          change: '+22.0% uplift',
          isPositive: true,
          description: 'Blazing fast mobile experiences drive higher conversion rates for early users.',
          details: { before: '77.1% Pass Rate', after: '99.1% Pass Rate', impact: '+14% checkout conversion' },
        },
        {
          id: 'maintenance',
          label: 'Design System Maintenance Cost',
          value: '-90%',
          baseline: 'Zero full-time design engineers needed',
          change: '-90% Overhead',
          isPositive: true,
          description: 'Founders and full-stack devs build world-class Linear-grade interfaces independently.',
          details: {
            before: 'Hiring UI designer ($140k)',
            after: 'Self-serve registry ($0)',
            impact: '$140k runway preserved',
          },
        },
        {
          id: 'cloud_savings',
          label: 'Average Annual Compute & Bandwidth Saved',
          value: '$48k',
          baseline: 'Vercel / Cloudflare edge usage tier',
          change: 'Sub-millisecond cold start',
          isPositive: true,
          description: 'Compact bundle sizes prevent edge function invocation timeouts and memory spikes.',
          details: { before: '350ms cold starts', after: '42ms cold starts', impact: '88% compute reduction' },
        },
      ],
    }
    
    const cohortOptions: { id: CustomerCohort; label: string }[] = [
      { id: 'all', label: 'All Teams' },
      { id: 'enterprise', label: 'Enterprise 500' },
      { id: 'startups', label: 'High-Growth' },
    ]
    
    @Component({
      changeDetection: ChangeDetectionStrategy.Eager,
      selector: 'ui-customer-metrics-band, [ui-customer-metrics-band]',
      standalone: true,
      // React renders the root <section> itself: the host stays out of layout and `class` goes to the root.
      host: { '[attr.class]': '"contents"' },
      imports: [LucideAngularModule, UiBadgeComponent, UiCardComponent],
      template: `
        <section data-slot="customer-metrics-band" [class]="rootClass">
          <div class="mx-auto max-w-6xl space-y-12">
            <!-- Section Header with Cohort Switcher -->
            <div
              class="border-border/60 flex flex-col items-center justify-between gap-6 border-b pb-8 text-center md:flex-row md:text-left"
            >
              <div class="space-y-2">
                <span ui-badge variant="secondary" class="gap-1.5 px-3 py-1 font-mono text-xs shadow-xs">
                  <lucide-icon [img]="Gauge" class="text-primary size-3.5" />
                  Verified Customer Impact Benchmark
                </span>
                <h2 class="text-foreground text-3xl font-bold tracking-tight">
                  Proven engineering velocity at global scale.
                </h2>
                <p class="text-muted-foreground text-xs sm:text-sm">
                  Aggregated telemetry from over 1,400+ production applications built with UIPKGE.
                </p>
              </div>
    
              <!-- Cohort Switcher Tabs -->
              <div class="bg-background border-border flex shrink-0 items-center gap-1 rounded-xl border p-1">
                @for (c of cohortOptions; track c.id) {
                  <button type="button" [class]="cohortButtonClass(c.id)" (click)="activeCohort.set(c.id)">
                    {{ c.label }}
                  </button>
                }
              </div>
            </div>
    
            <!-- 4-Tile KPI Metric Cards Grid -->
            <div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
              @for (stat of currentMetricList(); track stat.id) {
                <div
                  ui-card
                  role="button"
                  tabindex="0"
                  [attr.aria-pressed]="activeMetricId() === stat.id"
                  [class]="metricCardClass(stat.id)"
                  (click)="activeMetricId.set(stat.id)"
                  (keydown.enter)="activeMetricId.set(stat.id)"
                  (keydown.space)="selectMetricKey(stat.id, $event)"
                >
                  <div class="space-y-2">
                    <div class="flex items-center justify-between">
                      <span class="text-muted-foreground max-w-[160px] truncate font-mono text-xs">{{ stat.label }}</span>
                      <span
                        ui-badge
                        variant="outline"
                        class="border-success/20 bg-success/10 text-success font-mono text-xs"
                      >
                        {{ stat.change }}
                      </span>
                    </div>
                    <p class="text-foreground font-mono text-3xl font-bold tracking-tight sm:text-4xl">{{ stat.value }}</p>
                  </div>
    
                  <p class="text-muted-foreground border-border/60 border-t pt-2 text-xs leading-relaxed">
                    {{ stat.baseline }}
                  </p>
                </div>
              }
            </div>
    
            <!-- Interactive Detailed Breakdown Card for Selected KPI -->
            <div
              ui-card
              class="border-border bg-card/95 space-y-6 overflow-hidden rounded-2xl p-6 text-left shadow-xl backdrop-blur-md sm:p-8"
            >
              <div
                class="border-border flex flex-col items-start justify-between gap-4 border-b pb-4 sm:flex-row sm:items-center"
              >
                <div class="space-y-1">
                  <div class="flex items-center gap-2">
                    <span class="bg-primary size-2 rounded-full"></span>
                    <h3 class="text-foreground font-mono text-base font-bold">
                      {{ selectedMetric().label }} &mdash; Deep Dive
                    </h3>
                  </div>
                  <p class="text-muted-foreground text-xs">{{ selectedMetric().description }}</p>
                </div>
                <span ui-badge variant="secondary" class="text-primary font-mono text-xs font-semibold">
                  Cohort: {{ activeCohort().toUpperCase() }}
                </span>
              </div>
    
              <div class="grid grid-cols-1 gap-4 sm:grid-cols-3">
                <div class="border-border bg-background/50 space-y-1 rounded-xl border p-4">
                  <p class="text-muted-foreground font-mono text-xs">Monolithic Package Baseline</p>
                  <p class="text-muted-foreground font-mono text-lg font-bold line-through">
                    {{ selectedMetric().details.before }}
                  </p>
                </div>
                <div class="border-border bg-background/50 space-y-1 rounded-xl border p-4">
                  <p class="text-success font-mono text-xs font-semibold">UIPKGE Unbundled Registry</p>
                  <p class="text-success font-mono text-lg font-bold">
                    {{ selectedMetric().details.after }}
                  </p>
                </div>
                <div class="border-border bg-primary/5 space-y-1 rounded-xl border p-4">
                  <p class="text-primary font-mono text-xs font-semibold">Net Engineering Impact</p>
                  <p class="text-foreground font-mono text-lg font-bold">{{ selectedMetric().details.impact }}</p>
                </div>
              </div>
            </div>
          </div>
        </section>
      `,
    })
    export class UiCustomerMetricsBandComponent {
      protected readonly Gauge = Gauge
      protected readonly cohortOptions = cohortOptions
    
      @Input() set initialCohort(v: CustomerCohort) {
        if (v) this.activeCohort.set(v)
      }
      @Input() set initialMetricId(v: string) {
        if (v) this.activeMetricId.set(v)
      }
      @Input('class') className?: string
    
      readonly activeCohort = signal<CustomerCohort>('all')
      readonly activeMetricId = signal<string>('velocity')
    
      readonly currentMetricList = computed(() => metricsByCohort[this.activeCohort()])
      readonly selectedMetric = computed(() => {
        const list = this.currentMetricList()
        return list.find((m) => m.id === this.activeMetricId()) ?? list[0]
      })
    
      get rootClass(): string {
        return cn(
          'border-border/80 bg-muted/10 relative overflow-hidden border-y px-4 py-16 sm:px-6 sm:py-24 lg:px-8',
          this.className,
        )
      }
    
      cohortButtonClass(id: CustomerCohort): string {
        // [class] replaces static classes: the base must live here (matches React's cn(base, active)).
        return cn(
          'focus-visible:ring-ring rounded-lg px-3 py-1.5 font-mono text-xs transition-colors focus-visible:ring-2 focus-visible:outline-none',
          this.activeCohort() === id
            ? 'bg-primary text-primary-foreground font-semibold shadow-xs'
            : 'text-muted-foreground hover:text-foreground',
        )
      }
    
      metricCardClass(id: string): string {
        return cn(
          'border-border bg-card/90 focus-visible:ring-ring flex cursor-pointer flex-col justify-between space-y-4 rounded-2xl p-5 shadow-sm transition-colors hover:shadow-md focus-visible:ring-2 focus-visible:outline-none',
          this.activeMetricId() === id && 'ring-primary border-primary/60 bg-card ring-2',
        )
      }
    
      selectMetricKey(id: string, event: KeyboardEvent): void {
        event.preventDefault()
        this.activeMetricId.set(id)
      }
    }
    
  • src/app/components/blocks/customer-metrics-band/index.ts0.1 kB

Raw manifest:https://uipkge.dev/r/angular/customer-metrics-band.json