Menu

Change language

GitHub repo

Features — Visual Changelog Diff

blockfeature

Visual semantic git changelog with unified code diff highlighting and commit verification.

Installation

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

Variants

Loading interactive previews…

Schema

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

ChangelogDiffLine
interface ChangelogDiffLine {
  type: 'add' | 'delete' | 'context'
  content: string
}
ChangelogRelease
interface ChangelogRelease {
  version: string
  date: string
  title: string
  description: string
  commitSha: string
  diffLines: ChangelogDiffLine[]
}

npm dependencies

Includes

Files installed (2)

  • src/app/components/blocks/feature-visual-changelog-diff/feature-visual-changelog-diff.component.ts8.7 kB
    // Visual semantic git changelog with unified code diff highlighting and commit hash
    // copy verification. Port of the React block 1:1.
    import { ChangeDetectionStrategy, Component, DestroyRef, Input, computed, inject, signal } from '@angular/core'
    import { ArrowRight, Check, Copy, GitCommit, GitPullRequest, LucideAngularModule } from 'lucide-angular'
    import { cn } from '@/lib/utils'
    import { UiBadgeComponent } from '@/ui/badge/badge.component'
    import { UiCardComponent, UiCardContentComponent } from '@/ui/card/card.component'
    
    export interface ChangelogDiffLine {
      type: 'add' | 'delete' | 'context'
      content: string
    }
    
    export interface ChangelogRelease {
      version: string
      date: string
      title: string
      description: string
      commitSha: string
      diffLines: ChangelogDiffLine[]
    }
    
    const DEFAULT_RELEASES: ChangelogRelease[] = [
      {
        version: 'v2.4.0',
        date: 'August 2026',
        title: 'Tailwind CSS v4 & OKLCH Semantic Color Spaces',
        description:
          'Upgraded all cross-framework tokens to direct @theme inline CSS bindings. Eliminates legacy postcss configs.',
        commitSha: '7f9a2b1',
        diffLines: [
          { type: 'delete', content: '- module.exports = { theme: { extend: { colors: { ... } } } }' },
          { type: 'add', content: '+ @theme inline {' },
          { type: 'add', content: '+   --color-background: oklch(1 0 0);' },
          { type: 'add', content: '+   --color-primary: oklch(0.205 0 0);' },
          { type: 'add', content: '+ }' },
          { type: 'context', content: '  // Zero JavaScript compilation overhead in production bundles' },
        ],
      },
      {
        version: 'v2.3.0',
        date: 'July 2026',
        title: 'Headless Polymorphic Primitive Architecture',
        description: 'Unified Reka UI and Radix UI state machines across Vue 3.5 and React 19 mirrors.',
        commitSha: '4c8e1d9',
        diffLines: [
          { type: 'delete', content: '- import { Dialog } from "legacy-untyped-modal"' },
          { type: 'add', content: '+ import { DialogRoot, DialogPortal } from "reka-ui"' },
          { type: 'add', content: '+ <Primitive data-slot="dialog" :as="as">' },
          { type: 'context', content: '  // Full WCAG AA focus trapping and keyboard navigation' },
        ],
      },
    ]
    
    @Component({
      changeDetection: ChangeDetectionStrategy.Eager,
      selector: 'ui-feature-visual-changelog-diff, [ui-feature-visual-changelog-diff]',
      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, UiCardContentComponent],
      template: `
        <section data-slot="feature-visual-changelog-diff" [class]="rootClass">
          <div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
            <!-- Section Header -->
            <div class="mx-auto max-w-3xl space-y-4 text-center">
              <a
                href="#changelog"
                class="group border-border/80 bg-secondary/60 hover:bg-secondary text-foreground inline-flex items-center gap-2 rounded-full border px-3.5 py-1 text-xs font-medium shadow-2xs transition-colors"
              >
                <lucide-icon [img]="GitPullRequest" class="text-primary size-3.5" />
                <span>Continuous Release Ledger</span>
                <lucide-icon
                  [img]="ArrowRight"
                  class="text-muted-foreground size-3 transition-transform group-hover:translate-x-0.5"
                />
              </a>
    
              <h2 class="text-foreground text-3xl font-bold tracking-tight sm:text-4xl">
                {{ titleText() }}
              </h2>
    
              <p class="text-muted-foreground text-base sm:text-lg">
                {{ descriptionText() }}
              </p>
            </div>
    
            <!-- Changelog Timeline Entries -->
            <div class="mx-auto mt-12 max-w-4xl space-y-8">
              @for (rel of activeReleases(); track rel.version) {
                <div ui-card class="border-border bg-card/80 overflow-hidden shadow-xs backdrop-blur-xs">
                  <div ui-card-content class="space-y-5 p-6">
                    <!-- Header Bar -->
                    <div class="border-border flex flex-wrap items-center justify-between gap-2 border-b pb-3">
                      <div class="flex items-center gap-2.5">
                        <span
                          ui-badge
                          variant="outline"
                          class="border-primary/40 bg-primary/10 text-primary font-mono text-xs font-bold"
                        >
                          {{ rel.version }}
                        </span>
                        <span class="text-foreground text-sm font-bold">{{ rel.title }}</span>
                      </div>
    
                      <div class="flex items-center gap-2">
                        <span class="text-muted-foreground font-mono text-xs">{{ rel.date }}</span>
                        <button
                          type="button"
                          class="border-border bg-muted/30 text-muted-foreground hover:text-foreground inline-flex items-center gap-1 rounded border px-2 py-0.5 font-mono text-xs transition-colors"
                          (click)="copySha(rel.commitSha)"
                        >
                          <lucide-icon [img]="GitCommit" class="text-primary size-3" />
                          <span>{{ rel.commitSha }}</span>
                          @if (copiedSha() === rel.commitSha) {
                            <lucide-icon [img]="Check" class="text-success ml-0.5 size-3" />
                          } @else {
                            <lucide-icon [img]="Copy" class="ml-0.5 size-3" />
                          }
                        </button>
                      </div>
                    </div>
    
                    <p class="text-muted-foreground text-xs leading-relaxed">
                      {{ rel.description }}
                    </p>
    
                    <!-- Code Diff Box -->
                    <div class="border-border bg-muted/40 overflow-hidden rounded-lg border font-mono text-xs">
                      <div
                        class="border-border/80 bg-muted/60 text-muted-foreground flex items-center justify-between border-b px-3 py-1.5 text-xs font-bold"
                      >
                        <span>Unified Source Diff</span>
                        <span class="text-success text-xs font-normal">Git verified</span>
                      </div>
                      <div class="space-y-1 overflow-x-auto p-3 text-xs">
                        @for (line of rel.diffLines; track line.content) {
                          <div [class]="diffLineClass(line.type)">
                            {{ line.content }}
                          </div>
                        }
                      </div>
                    </div>
                  </div>
                </div>
              }
            </div>
          </div>
        </section>
      `,
    })
    export class UiFeatureVisualChangelogDiffComponent {
      protected readonly ArrowRight = ArrowRight
      protected readonly Check = Check
      protected readonly Copy = Copy
      protected readonly GitCommit = GitCommit
      protected readonly GitPullRequest = GitPullRequest
    
      @Input() set title(v: string) {
        this.titleText.set(v ?? 'Continuous evolution with transparent source-level changelogs.')
      }
      @Input() set description(v: string) {
        this.descriptionText.set(
          v ??
            'Every architectural improvement, performance optimization, and token adjustment documented with precise git diffs.',
        )
      }
      @Input() set releases(v: ChangelogRelease[] | undefined) {
        this.releasesInput.set(v)
      }
      @Input('class') className?: string
    
      readonly titleText = signal('Continuous evolution with transparent source-level changelogs.')
      readonly descriptionText = signal(
        'Every architectural improvement, performance optimization, and token adjustment documented with precise git diffs.',
      )
      private readonly releasesInput = signal<ChangelogRelease[] | undefined>(undefined)
      readonly copiedSha = signal<string | null>(null)
      private copyTimer: ReturnType<typeof setTimeout> | undefined
    
      constructor() {
        inject(DestroyRef).onDestroy(() => clearTimeout(this.copyTimer))
      }
    
      readonly activeReleases = computed(() => this.releasesInput() ?? DEFAULT_RELEASES)
    
      get rootClass(): string {
        return cn('bg-background relative overflow-hidden py-16 sm:py-24', this.className)
      }
    
      diffLineClass(type: ChangelogDiffLine['type']): string {
        return cn(
          'rounded px-2 py-0.5 leading-relaxed',
          type === 'add'
            ? 'bg-success/10 text-success font-medium'
            : type === 'delete'
              ? 'bg-destructive/10 text-destructive text-destructive line-through opacity-80'
              : 'text-muted-foreground',
        )
      }
    
      async copySha(sha: string): Promise<void> {
        try {
          await navigator.clipboard.writeText(sha)
          this.copiedSha.set(sha)
          clearTimeout(this.copyTimer)
          this.copyTimer = setTimeout(() => this.copiedSha.set(null), 2000)
        } catch {
          // Clipboard unavailable (e.g. insecure context): leave the icon unchanged.
        }
      }
    }
    
  • src/app/components/blocks/feature-visual-changelog-diff/index.ts0.1 kB

Raw manifest:https://uipkge.dev/r/angular/feature-visual-changelog-diff.json