Menu

Change language

GitHub repo

Testimonial Masonry Verified Grid

blockmarketing

Masonry grid of verified engineer endorsements with role filters, star ratings, avatar badges, and quantifiable impact metrics.

Installation

$npx uipkge-ng@latest add testimonial-masonry-verified-grid
Manifest:https://uipkge.dev/r/angular/testimonial-masonry-verified-grid.jsonInstalls to:src/app/components/blocks/testimonial-masonry-verified-grid/

Variants

Loading interactive previews…

Schema

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

VerifiedTestimonial
interface VerifiedTestimonial {
  id: string
  name: string
  role: string
  roleCategory: 'frontend' | 'founder' | 'design-eng'
  company: string
  avatar: string
  handle: string
  verifiedSource: 'Twitter' | 'GitHub' | 'LinkedIn'
  quote: string
  metricBadge?: string
  starCount: number
}

npm dependencies

Includes

Files installed (2)

  • src/app/components/blocks/testimonial-masonry-verified-grid/testimonial-masonry-verified-grid.component.ts9.8 kB
    // Masonry grid of verified engineer endorsements with role filters, star ratings,
    // avatar badges, and quantifiable impact metrics. Port of the Vue/React block 1:1.
    import { ChangeDetectionStrategy, Component, Input, computed, signal } from '@angular/core'
    import { CheckCircle2, LucideAngularModule, Star } from 'lucide-angular'
    import { cn } from '@/lib/utils'
    import { UiBadgeComponent } from '@/ui/badge/badge.component'
    import { UiCardComponent } from '@/ui/card/card.component'
    
    export type MasonryRoleFilter = 'all' | 'frontend' | 'founder' | 'design-eng'
    
    export interface VerifiedTestimonial {
      id: string
      name: string
      role: string
      roleCategory: 'frontend' | 'founder' | 'design-eng'
      company: string
      avatar: string
      handle: string
      verifiedSource: 'Twitter' | 'GitHub' | 'LinkedIn'
      quote: string
      metricBadge?: string
      starCount: number
    }
    
    const testimonials: VerifiedTestimonial[] = [
      {
        id: '1',
        name: 'Alexandre Rivière',
        role: 'Principal Design Engineer',
        roleCategory: 'design-eng',
        company: 'Linear Ecosystem',
        avatar: 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=150&auto=format&fit=crop&q=80',
        handle: '@alex_riviere',
        verifiedSource: 'Twitter',
        quote:
          'The unbundled component model completely cured our team from npm dependency fatigue. We get raw SFCs and TSX with exact Tailwind v4 token alignments. Zero wrapper bloat, 100% code ownership.',
        metricBadge: '4.2x Faster Ship Velocity',
        starCount: 5,
      },
      {
        id: '2',
        name: 'Sarah Chen',
        role: 'VP of Engineering',
        roleCategory: 'founder',
        company: 'ScaleDev AI',
        avatar: 'https://images.unsplash.com/photo-1580489944761-15a19d654956?w=150&auto=format&fit=crop&q=80',
        handle: '@schen_ai',
        verifiedSource: 'GitHub',
        quote:
          'We had severe bundle bloat with our previous UI package (over 500kB of unused JS). Switching to UIPKGE reduced our initial bundle to 38kB and solved our INP scores overnight.',
        metricBadge: '-88% Bundle Size',
        starCount: 5,
      },
      {
        id: '3',
        name: 'Marcus Vance',
        role: 'Staff Frontend Architect',
        roleCategory: 'frontend',
        company: 'HyperQubit Cloud',
        avatar: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=150&auto=format&fit=crop&q=80',
        handle: '@marcus_vance',
        verifiedSource: 'Twitter',
        quote:
          'Dual-framework parity is not a gimmick here—it is mathematically verified. We maintain a Nuxt 3 admin console and Next.js customer portal with identical design tokens and micro-interactions.',
        metricBadge: '100% Token Parity',
        starCount: 5,
      },
      {
        id: '4',
        name: 'Elena Rostova',
        role: 'Head of Product Design',
        roleCategory: 'design-eng',
        company: 'Fintech Velocity',
        avatar: 'https://images.unsplash.com/photo-1517841905240-472988babdf9?w=150&auto=format&fit=crop&q=80',
        handle: '@elena_craft',
        verifiedSource: 'GitHub',
        quote:
          'The spring physics and micro-interactions match Linear and Raycast levels of craft. No arbitrary sub-12px micro-text or sloppy contrast issues.',
        metricBadge: 'WCAG AA AA Certified',
        starCount: 5,
      },
      {
        id: '5',
        name: 'David Kim',
        role: 'Founder & CTO',
        roleCategory: 'founder',
        company: 'PulseOps',
        avatar: 'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=150&auto=format&fit=crop&q=80',
        handle: '@dkim_ops',
        verifiedSource: 'Twitter',
        quote:
          'Being able to run `npx shadcn-vue add` and have clean, pristine components in our git repository is the single biggest DX breakthrough since Vite.',
        metricBadge: 'Zero Upstream Lock-in',
        starCount: 5,
      },
      {
        id: '6',
        name: 'Liam O’Connor',
        role: 'Lead UI Engineer',
        roleCategory: 'frontend',
        company: 'Starlight Media',
        avatar: 'https://images.unsplash.com/photo-1522075469751-3a6694fb2f61?w=150&auto=format&fit=crop&q=80',
        handle: '@liam_dev',
        verifiedSource: 'GitHub',
        quote:
          'The marketing blocks are actually functional workbenches with live sliders, real SVG telemetry sparklines, and zero dummy shapes. Huge time saver.',
        metricBadge: '450+ Verified Blocks',
        starCount: 5,
      },
    ]
    
    @Component({
      changeDetection: ChangeDetectionStrategy.Eager,
      selector: 'ui-testimonial-masonry-verified-grid, [ui-testimonial-masonry-verified-grid]',
      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="testimonial-masonry-verified-grid" [class]="rootClass">
          <div class="mx-auto max-w-7xl space-y-12">
            <!-- Section Header -->
            <div class="mx-auto max-w-3xl space-y-4 text-center">
              <span ui-badge variant="secondary" class="gap-1.5 px-3 py-1 font-mono text-xs shadow-xs">
                <lucide-icon [img]="Star" class="fill-warning text-warning size-3.5" />
                Verified Engineer Endorsements
              </span>
              <h2 class="text-foreground text-3xl font-bold tracking-tight sm:text-4xl">
                Loved by design engineers and CTOs worldwide.
              </h2>
              <p class="text-muted-foreground text-base">
                Real feedback from engineers who dumped monolithic npm packages for unbundled code ownership.
              </p>
    
              <!-- Category Filters -->
              <div class="flex flex-wrap items-center justify-center gap-2 pt-2">
                <button type="button" [class]="filterClass('all')" (click)="setFilter('all')">
                  All Perspectives ({{ totalCount }})
                </button>
                <button type="button" [class]="filterClass('design-eng')" (click)="setFilter('design-eng')">
                  Design Engineers
                </button>
                <button type="button" [class]="filterClass('frontend')" (click)="setFilter('frontend')">
                  Frontend Architects
                </button>
                <button type="button" [class]="filterClass('founder')" (click)="setFilter('founder')">
                  CTOs &amp; Founders
                </button>
              </div>
            </div>
    
            <!-- Masonry Grid (3 Columns) -->
            <div class="grid grid-cols-1 items-start gap-6 md:grid-cols-2 lg:grid-cols-3">
              @for (t of filteredTestimonials(); track t.id) {
                <div
                  ui-card
                  class="border-border bg-card/95 hover:border-primary/40 group relative flex flex-col justify-between space-y-4 overflow-hidden rounded-2xl p-6 text-left shadow-lg transition-colors hover:shadow-xl"
                >
                  <!-- Top Row: Avatar, Name, Handle, Verified Badge -->
                  <div class="flex items-start justify-between gap-3">
                    <div class="flex items-center gap-3">
                      <img
                        [src]="t.avatar"
                        [alt]="t.name"
                        class="border-border size-10 shrink-0 rounded-full border object-cover"
                      />
                      <div class="min-w-0">
                        <div class="flex items-center gap-1.5">
                          <h4 class="text-foreground truncate font-mono text-xs font-bold">{{ t.name }}</h4>
                          <lucide-icon [img]="CheckCircle2" class="text-info size-3.5 shrink-0" />
                        </div>
                        <p class="text-muted-foreground truncate text-xs">{{ t.role }} &bull; {{ t.company }}</p>
                      </div>
                    </div>
    
                    <span ui-badge variant="outline" class="text-muted-foreground shrink-0 font-mono text-xs">
                      {{ t.verifiedSource }}
                    </span>
                  </div>
    
                  <!-- Quote text -->
                  <p class="text-foreground/90 text-xs leading-relaxed italic sm:text-sm">&ldquo;{{ t.quote }}&rdquo;</p>
    
                  <!-- Footer Row: Star Rating & Impact Metric Badge -->
                  <div class="border-border/60 flex flex-wrap items-center justify-between gap-2 border-t pt-3">
                    <div class="flex items-center gap-0.5">
                      @for (s of stars(t.starCount); track s) {
                        <lucide-icon [img]="Star" class="fill-warning text-warning size-3.5" />
                      }
                    </div>
    
                    @if (t.metricBadge) {
                      <span
                        ui-badge
                        variant="outline"
                        class="border-success/20 bg-success/10 text-success font-mono text-xs"
                      >
                        {{ t.metricBadge }}
                      </span>
                    }
                  </div>
                </div>
              }
            </div>
          </div>
        </section>
      `,
    })
    export class UiTestimonialMasonryVerifiedGridComponent {
      protected readonly CheckCircle2 = CheckCircle2
      protected readonly Star = Star
    
      @Input() set initialFilter(v: MasonryRoleFilter) {
        this.activeFilter.set(v ?? 'all')
      }
      @Input('class') className?: string
    
      readonly activeFilter = signal<MasonryRoleFilter>('all')
    
      readonly filteredTestimonials = computed(() => {
        const f = this.activeFilter()
        if (f === 'all') return testimonials
        return testimonials.filter((t) => t.roleCategory === f)
      })
    
      get totalCount(): number {
        return testimonials.length
      }
    
      get rootClass(): string {
        return cn('bg-background relative overflow-hidden px-4 py-16 sm:px-6 sm:py-24 lg:px-8', this.className)
      }
    
      setFilter(f: MasonryRoleFilter): void {
        this.activeFilter.set(f)
      }
    
      filterClass(f: MasonryRoleFilter): string {
        return cn(
          'rounded-lg border px-3.5 py-1 font-mono text-xs transition-colors',
          this.activeFilter() === f
            ? 'border-primary bg-primary text-primary-foreground font-semibold shadow-xs'
            : 'border-border bg-card text-muted-foreground hover:text-foreground',
        )
      }
    
      stars(count: number): number[] {
        return Array.from({ length: count }, (_, i) => i)
      }
    }
    
  • src/app/components/blocks/testimonial-masonry-verified-grid/index.ts0.2 kB

Raw manifest:https://uipkge.dev/r/angular/testimonial-masonry-verified-grid.json