Menu

Change language

GitHub repo

Code — Diff Review

blockmarketing

Unified diff panel with added and removed lines, a file path header, a reviewer note anchored to a line, and a summary bar counting the changes.

Installation

$npx uipkge-ng@latest add code-diff-review
Manifest:https://uipkge.dev/r/angular/code-diff-review.jsonInstalls to:src/app/components/blocks/code-diff-review/

Variants

Loading interactive previews…

Schema

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

DiffLine
interface DiffLine {
  old: number | null
  new: number | null
  tone: DiffTone
  text: string
}

Files installed (2)

  • src/app/components/blocks/code-diff-review/code-diff-review.component.ts6.4 kB
    // Unified diff panel with added and removed lines, a file path header, a reviewer note
    // anchored to a line, and a summary bar counting the changes. Port of the React block 1:1.
    import { ChangeDetectionStrategy, Component, Input, computed } from '@angular/core'
    import { Check, FileDiff, LucideAngularModule, MessageSquare } from 'lucide-angular'
    import { cn } from '@/lib/utils'
    import { UiAvatarComponent, UiAvatarFallbackComponent } from '@/ui/avatar/avatar.component'
    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'
    
    type DiffTone = 'same' | 'add' | 'remove'
    
    interface DiffLine {
      old: number | null
      new: number | null
      tone: DiffTone
      text: string
    }
    
    // tone drives the row treatment, so individual lines carry no styling of their own.
    const hunk: DiffLine[] = [
      { old: 12, new: 12, tone: 'same', text: 'metric revenue_net {' },
      { old: 13, new: 13, tone: 'same', text: '  owner: finance-analytics' },
      { old: 14, new: null, tone: 'remove', text: '  window: trailing_30d' },
      { old: null, new: 14, tone: 'add', text: '  window: trailing_28d' },
      { old: null, new: 15, tone: 'add', text: '  restated_from: 2026-01-01' },
      { old: 15, new: 16, tone: 'same', text: '  source: warehouse.orders' },
      { old: 16, new: 17, tone: 'same', text: '}' },
    ]
    
    @Component({
      changeDetection: ChangeDetectionStrategy.Eager,
      selector: 'ui-code-diff-review, [ui-code-diff-review]',
      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,
        UiAvatarComponent,
        UiAvatarFallbackComponent,
        UiBadgeComponent,
        UiButtonComponent,
        UiCardComponent,
        UiCardContentComponent,
        UiSeparatorComponent,
      ],
      template: `
        <section data-slot="code-diff-review" class="bg-background">
          <div class="mx-auto max-w-3xl px-6 py-20">
            <span ui-badge variant="secondary" class="gap-1.5">
              <lucide-icon [img]="FileDiff" class="size-3" aria-hidden="true" />
              Change review
            </span>
            <h2 class="mt-4 text-2xl font-semibold tracking-tight">Every metric change is a reviewable diff</h2>
            <p class="text-muted-foreground mt-2">
              Definitions live in the repo, so changing one opens a pull request with an owner and a revert path.
            </p>
    
            <div ui-card class="mt-8">
              <div ui-card-content class="p-0">
                <div class="border-border flex items-center gap-3 border-b px-4 py-2.5">
                  <code class="min-w-0 truncate font-mono text-xs">metrics/revenue_net.yml</code>
                  <div class="ml-auto flex shrink-0 items-center gap-2 font-mono text-xs">
                    <span class="text-success">+{{ added() }}</span>
                    <span class="text-destructive">−{{ removed() }}</span>
                  </div>
                </div>
    
                <div class="overflow-x-auto">
                  <table class="w-full border-collapse font-mono text-xs">
                    <caption class="sr-only">
                      Unified diff for metrics/revenue_net.yml
                    </caption>
                    <tbody>
                      @for (line of lines; track line.text; let i = $index) {
                        <tr [class]="rowClass(line.tone)">
                          <td class="text-muted-foreground/60 w-10 px-2 py-1 text-right select-none">
                            {{ line.old ?? '' }}
                          </td>
                          <td class="text-muted-foreground/60 w-10 px-2 py-1 text-right select-none">
                            {{ line.new ?? '' }}
                          </td>
                          <td class="w-4 py-1 pl-2 select-none">{{ marker(line.tone) }}</td>
                          <td class="py-1 pr-4 whitespace-pre">{{ line.text }}</td>
                        </tr>
                      }
                    </tbody>
                  </table>
                </div>
    
                <ui-separator />
    
                <div class="flex items-start gap-3 px-4 py-3">
                  <ui-avatar class="size-7">
                    <ui-avatar-fallback class="text-xs">EW</ui-avatar-fallback>
                  </ui-avatar>
                  <div class="min-w-0">
                    <p class="text-xs">
                      <span class="font-medium">Erin Walsh</span>
                      <span class="text-muted-foreground"> on line 14</span>
                    </p>
                    <p class="text-muted-foreground mt-1 text-xs leading-relaxed">
                      Restating from January means Q1 reports change. Flag it in the release note before this merges.
                    </p>
                  </div>
                  <button ui-button variant="ghost" size="sm" class="ml-auto h-7 shrink-0 gap-1.5 text-xs">
                    <lucide-icon [img]="MessageSquare" class="size-3" aria-hidden="true" />
                    Reply
                  </button>
                </div>
              </div>
            </div>
    
            <div class="text-muted-foreground mt-4 flex flex-wrap items-center gap-x-5 gap-y-2 text-xs">
              <span class="flex items-center gap-1.5">
                <lucide-icon [img]="Check" class="text-success size-3.5" aria-hidden="true" />
                Reconciliation passed against the prior four quarters
              </span>
              <span class="flex items-center gap-1.5">
                <lucide-icon [img]="Check" class="text-success size-3.5" aria-hidden="true" />
                41 downstream consumers identified
              </span>
            </div>
          </div>
        </section>
      `,
    })
    export class UiCodeDiffReviewComponent {
      protected readonly Check = Check
      protected readonly FileDiff = FileDiff
      protected readonly MessageSquare = MessageSquare
    
      protected readonly lines = hunk
    
      @Input('class') className?: string
    
      readonly added = computed(() => hunk.filter((line) => line.tone === 'add').length)
      readonly removed = computed(() => hunk.filter((line) => line.tone === 'remove').length)
    
      get rootClass(): string {
        return cn('w-full', this.className)
      }
    
      rowClass(tone: DiffTone): string {
        if (tone === 'add') return 'bg-success/10 text-foreground'
        if (tone === 'remove') return 'bg-destructive/10 text-muted-foreground'
        return 'text-muted-foreground'
      }
    
      marker(tone: DiffTone): string {
        return tone === 'add' ? '+' : tone === 'remove' ? '-' : ' '
      }
    }
    
  • src/app/components/blocks/code-diff-review/index.ts0.1 kB

Raw manifest:https://uipkge.dev/r/angular/code-diff-review.json