Menu

Change language

GitHub repo

Comparison — Migration Path

blockmarketing

Migration-focused comparison pairing what a team keeps, what changes, and what is retired when moving from an existing tool, with an effort estimate per row.

Installation

$npx uipkge-ng@latest add comparison-migration-path
Manifest:https://uipkge.dev/r/angular/comparison-migration-path.jsonInstalls to:src/app/components/blocks/comparison-migration-path/

Variants

Loading interactive previews…

Schema

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

MigrationPathRow
interface MigrationPathRow {
  area: string
  verdict: MigrationVerdict
  detail: string
  effort: string
}

Files installed (2)

  • src/app/components/blocks/comparison-migration-path/comparison-migration-path.component.ts6.2 kB
    // Migration-focused comparison pairing what a team keeps, what changes, and what is
    // retired when moving from an existing tool, with an effort estimate per row.
    // Port of the React block 1:1.
    import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
    import { ArrowRight, Check, LucideAngularModule, Minus, RefreshCw } from 'lucide-angular'
    import { cn } from '@/lib/utils'
    import { UiBadgeComponent } from '@/ui/badge/badge.component'
    import { UiButtonComponent } from '@/ui/button/button.component'
    import { UiCardComponent, UiCardContentComponent } from '@/ui/card/card.component'
    import { UiSeparatorComponent } from '@/ui/separator/separator.component'
    import {
      UiTableBodyComponent,
      UiTableCellComponent,
      UiTableComponent,
      UiTableHeadComponent,
      UiTableHeaderComponent,
      UiTableRowComponent,
    } from '@/ui/table/table.component'
    
    export type MigrationVerdict = 'keep' | 'change' | 'retire'
    
    export interface MigrationPathRow {
      area: string
      verdict: MigrationVerdict
      detail: string
      effort: string
    }
    
    // verdict drives the icon and tone, so rows stay declarative and a new row
    // cannot forget to style itself.
    const defaultRows: MigrationPathRow[] = [
      {
        area: 'Warehouse and models',
        verdict: 'keep',
        detail: 'Untouched. We read from them, nothing is rewritten.',
        effort: 'None',
      },
      {
        area: 'dbt project',
        verdict: 'keep',
        detail: 'Imported as metric sources; your DAG keeps running.',
        effort: 'Half a day',
      },
      {
        area: 'Dashboard layouts',
        verdict: 'change',
        detail: 'Rebuilt against certified metrics, usually faster than the original.',
        effort: '1–2 weeks',
      },
      {
        area: 'Metric formulas',
        verdict: 'change',
        detail: 'Moved from dashboard config into versioned files.',
        effort: '1 week',
      },
      {
        area: 'Per-dashboard permissions',
        verdict: 'retire',
        detail: 'Replaced by row-level scope from your IdP.',
        effort: 'None',
      },
      {
        area: 'Bespoke extract jobs',
        verdict: 'retire',
        detail: 'Deleted once the metrics they fed are certified.',
        effort: 'None',
      },
    ]
    
    const verdictMeta: Record<MigrationVerdict, { label: string; class: string }> = {
      keep: { label: 'Keep', class: 'text-success' },
      change: { label: 'Change', class: 'text-muted-foreground' },
      retire: { label: 'Retire', class: 'text-muted-foreground/60' },
    }
    
    @Component({
      changeDetection: ChangeDetectionStrategy.Eager,
      selector: 'ui-comparison-migration-path, [ui-comparison-migration-path]',
      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,
        UiButtonComponent,
        UiCardComponent,
        UiCardContentComponent,
        UiSeparatorComponent,
        UiTableBodyComponent,
        UiTableCellComponent,
        UiTableComponent,
        UiTableHeadComponent,
        UiTableHeaderComponent,
        UiTableRowComponent,
      ],
      template: `
        <section data-slot="comparison-migration-path" [class]="rootClass">
          <div class="mx-auto max-w-5xl px-6 py-20 lg:py-28">
            <div class="max-w-2xl">
              <span ui-badge variant="secondary">Migration</span>
              <h2 class="mt-4 text-3xl font-semibold tracking-tight sm:text-4xl">What actually changes</h2>
              <p class="text-muted-foreground mt-3 text-lg">
                Most of the stack stays exactly where it is. The effort column is measured from real rollouts.
              </p>
            </div>
    
            <div ui-card class="mt-10">
              <div ui-card-content class="p-0">
                <table ui-table>
                  <thead ui-table-header>
                    <tr ui-table-row>
                      <th ui-table-head>Area</th>
                      <th ui-table-head class="w-28">Verdict</th>
                      <th ui-table-head>What happens</th>
                      <th ui-table-head class="w-32 text-right">Effort</th>
                    </tr>
                  </thead>
                  <tbody ui-table-body>
                    @for (row of rows; track row.area) {
                      <tr ui-table-row>
                        <td ui-table-cell class="font-medium">{{ row.area }}</td>
                        <td ui-table-cell>
                          <span [class]="'inline-flex items-center gap-1.5 text-xs ' + verdictClass(row.verdict)">
                            @if (row.verdict === 'keep') {
                              <lucide-icon [img]="Check" class="size-3.5" aria-hidden="true" />
                            } @else if (row.verdict === 'change') {
                              <lucide-icon [img]="RefreshCw" class="size-3.5" aria-hidden="true" />
                            } @else {
                              <lucide-icon [img]="Minus" class="size-3.5" aria-hidden="true" />
                            }
                            {{ verdictLabel(row.verdict) }}
                          </span>
                        </td>
                        <td ui-table-cell class="text-muted-foreground text-sm">{{ row.detail }}</td>
                        <td ui-table-cell class="text-right font-mono text-xs">{{ row.effort }}</td>
                      </tr>
                    }
                  </tbody>
                </table>
    
                <ui-separator />
                <p class="text-muted-foreground px-6 py-4 text-xs leading-relaxed">
                  Effort assumes one engineer and one analyst part-time. Teams with more than about 200 existing dashboards
                  usually stage the rebuild by department rather than doing it in one pass.
                </p>
              </div>
            </div>
    
            <button ui-button class="mt-6">
              Get a migration estimate
              <lucide-icon [img]="ArrowRight" class="ml-2 size-4" aria-hidden="true" />
            </button>
          </div>
        </section>
      `,
    })
    export class UiComparisonMigrationPathComponent {
      protected readonly ArrowRight = ArrowRight
      protected readonly Check = Check
      protected readonly Minus = Minus
      protected readonly RefreshCw = RefreshCw
    
      @Input() rows: MigrationPathRow[] = defaultRows
      @Input('class') className?: string
    
      get rootClass(): string {
        return cn('bg-background', this.className)
      }
    
      verdictLabel(v: MigrationVerdict): string {
        return verdictMeta[v].label
      }
    
      verdictClass(v: MigrationVerdict): string {
        return verdictMeta[v].class
      }
    }
    
  • src/app/components/blocks/comparison-migration-path/index.ts0.1 kB

Raw manifest:https://uipkge.dev/r/angular/comparison-migration-path.json