Menu

Change language

GitHub repo

Technical Specs Sheet

blockecommerce

Comprehensive engineering technical specifications sheet with Metric/Imperial SI toggles, parameter category filters, search, and downloadable PDF trigger.

Installation

$npx uipkge-ng@latest add technical-specs-sheet
Manifest:https://uipkge.dev/r/angular/technical-specs-sheet.jsonInstalls to:src/app/components/blocks/technical-specs-sheet/

Variants

Loading interactive previews…

Schema

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

SpecItem
interface SpecItem {
  id: string
  category: SpecCategory
  parameter: string
  metricValue: string
  imperialValue: string
  tolerance?: string
  testCondition?: string
  highlight?: boolean
}
CategoryOption
interface CategoryOption {
  id: 'all' | SpecCategory
  label: string
}

Files installed (2)

  • src/app/components/blocks/technical-specs-sheet/technical-specs-sheet.component.ts14.3 kB
    // Laboratory-grade engineering specification sheet with Metric/Imperial SI
    // toggles, parameter category filters, search, and downloadable PDF trigger.
    // Port of the React block 1:1.
    import { ChangeDetectionStrategy, Component, Input, computed, signal } from '@angular/core'
    import { CheckCircle2, FileDown, Info, LucideAngularModule, Search, ShieldCheck } 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,
      UiCardDescriptionComponent,
      UiCardHeaderComponent,
      UiCardTitleComponent,
    } from '@/ui/card/card.component'
    import { UiInputComponent } from '@/ui/input/input.component'
    import {
      UiTableBodyComponent,
      UiTableCellComponent,
      UiTableComponent,
      UiTableHeadComponent,
      UiTableHeaderComponent,
      UiTableRowComponent,
    } from '@/ui/table/table.component'
    
    export type SpecCategory = 'acoustic' | 'electrical' | 'physical' | 'connectivity' | 'compliance'
    
    export interface SpecItem {
      id: string
      category: SpecCategory
      parameter: string
      metricValue: string
      imperialValue: string
      tolerance?: string
      testCondition?: string
      highlight?: boolean
    }
    
    const defaultSpecs: SpecItem[] = [
      // Acoustic
      {
        id: 's1',
        category: 'acoustic',
        parameter: 'Transducer Type',
        metricValue: '50mm Planar-Magnetic with Neodymium N52 Array',
        imperialValue: '1.97 in Planar-Magnetic with Neodymium N52 Array',
        highlight: true,
      },
      {
        id: 's2',
        category: 'acoustic',
        parameter: 'Frequency Response',
        metricValue: '5 Hz – 52,000 Hz',
        imperialValue: '5 Hz – 52,000 Hz',
        tolerance: '± 0.5 dB (20 Hz - 20 kHz)',
        highlight: true,
      },
      {
        id: 's3',
        category: 'acoustic',
        parameter: 'Total Harmonic Distortion (THD)',
        metricValue: '< 0.04% @ 1 kHz, 100 dB SPL',
        imperialValue: '< 0.04% @ 1 kHz, 100 dB SPL',
        testCondition: 'IEC 60268-7 Standard',
      },
      {
        id: 's4',
        category: 'acoustic',
        parameter: 'Sound Pressure Level (SPL)',
        metricValue: '106 dB / 1 mW @ 1 kHz',
        imperialValue: '106 dB / 1 mW @ 1 kHz',
        testCondition: 'Free-field calibrated',
      },
      {
        id: 's5',
        category: 'acoustic',
        parameter: 'Acoustic Enclosure',
        metricValue: 'Open-Back CNC Milled Aircraft-Grade Aluminum Grille',
        imperialValue: 'Open-Back CNC Milled Aircraft-Grade Aluminum Grille',
      },
      // Electrical
      {
        id: 's6',
        category: 'electrical',
        parameter: 'Nominal Impedance',
        metricValue: '32 Ω pure resistive load',
        imperialValue: '32 Ω pure resistive load',
        tolerance: '± 5% @ 1 kHz',
        highlight: true,
      },
      {
        id: 's7',
        category: 'electrical',
        parameter: 'Maximum Input Power Handling',
        metricValue: '3,000 mW (3.0 W Continuous)',
        imperialValue: '3,000 mW (3.0 W Continuous)',
        testCondition: 'Thermal peak 5,000 mW (100ms)',
      },
      {
        id: 's8',
        category: 'electrical',
        parameter: 'Recommended Amplifier Output',
        metricValue: '250 mW – 2,000 mW @ 32 Ω',
        imperialValue: '250 mW – 2,000 mW @ 32 Ω',
      },
      // Physical
      {
        id: 's9',
        category: 'physical',
        parameter: 'Weight (excluding cable)',
        metricValue: '385 grams',
        imperialValue: '13.58 oz (0.85 lbs)',
        tolerance: '± 5g',
        highlight: true,
      },
      {
        id: 's10',
        category: 'physical',
        parameter: 'Ear Cushion Dimensions (Inner ID)',
        metricValue: '68 mm (H) × 52 mm (W) × 26 mm (D)',
        imperialValue: '2.68 in (H) × 2.05 in (W) × 1.02 in (D)',
      },
      {
        id: 's11',
        category: 'physical',
        parameter: 'Headband Clamping Force',
        metricValue: '4.2 Newtons',
        imperialValue: '0.94 lbf (Pound-force)',
        tolerance: '± 0.3 N',
      },
      {
        id: 's12',
        category: 'physical',
        parameter: 'Earpad Material Composition',
        metricValue: 'Perforated Lambskin + Cooling Gel Memory Foam Core',
        imperialValue: 'Perforated Lambskin + Cooling Gel Memory Foam Core',
      },
      // Connectivity
      {
        id: 's13',
        category: 'connectivity',
        parameter: 'Headphone Terminals',
        metricValue: 'Dual 3.5mm Gold-Plated TRRS Balanced Sockets',
        imperialValue: 'Dual 1/8 in Gold-Plated TRRS Balanced Sockets',
      },
      {
        id: 's14',
        category: 'connectivity',
        parameter: 'Included Studio Cable',
        metricValue: '2.5 meter 8-Core Monocrystalline Silver-Plated Copper (OCC)',
        imperialValue: '8.20 feet 8-Core Monocrystalline Silver-Plated Copper (OCC)',
        highlight: true,
      },
      {
        id: 's15',
        category: 'connectivity',
        parameter: 'Source Termination',
        metricValue: '4.4mm Pentaconn Balanced + 6.35mm Gold-Plated Adapter',
        imperialValue: '4.4mm Pentaconn Balanced + 1/4 in Gold-Plated Adapter',
      },
      // Compliance
      {
        id: 's16',
        category: 'compliance',
        parameter: 'Certifications & Directives',
        metricValue: 'Hi-Res Audio Certified, CE Mark, FCC Class B, RoHS 3, WEEE',
        imperialValue: 'Hi-Res Audio Certified, CE Mark, FCC Class B, RoHS 3, WEEE',
        highlight: true,
      },
      {
        id: 's17',
        category: 'compliance',
        parameter: 'Operating Temperature & Humidity',
        metricValue: '-10°C to +45°C (10% to 90% RH non-condensing)',
        imperialValue: '14°F to 113°F (10% to 90% RH non-condensing)',
      },
    ]
    
    interface CategoryOption {
      id: 'all' | SpecCategory
      label: string
    }
    
    const categories: CategoryOption[] = [
      { id: 'all', label: 'All Specifications' },
      { id: 'acoustic', label: 'Acoustic Architecture' },
      { id: 'electrical', label: 'Electrical & Power' },
      { id: 'physical', label: 'Dimensions & Weight' },
      { id: 'connectivity', label: 'Cables & Interconnects' },
      { id: 'compliance', label: 'Standards & Compliance' },
    ]
    
    @Component({
      changeDetection: ChangeDetectionStrategy.Eager,
      selector: 'ui-technical-specs-sheet, [ui-technical-specs-sheet]',
      standalone: true,
      // React renders the root Card itself: the host stays out of layout and `class` goes to the root.
      host: { '[attr.class]': '"contents"' },
      imports: [
        LucideAngularModule,
        UiBadgeComponent,
        UiButtonComponent,
        UiCardComponent,
        UiCardContentComponent,
        UiCardDescriptionComponent,
        UiCardHeaderComponent,
        UiCardTitleComponent,
        UiInputComponent,
        UiTableBodyComponent,
        UiTableCellComponent,
        UiTableComponent,
        UiTableHeadComponent,
        UiTableHeaderComponent,
        UiTableRowComponent,
      ],
      template: `
        <div ui-card data-slot="technical-specs-sheet" [class]="rootClass">
          <div ui-card-header class="border-border bg-muted/20 border-b pb-4">
            <div class="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
              <div>
                <div class="flex items-center gap-2">
                  <span class="text-muted-foreground font-mono text-xs font-semibold tracking-wider uppercase">
                    {{ modelNumber }} · {{ revision }}
                  </span>
                  <span ui-badge variant="outline" class="text-success font-mono text-xs"> Laboratory Certified </span>
                </div>
                <h3 ui-card-title class="text-foreground text-lg font-semibold tracking-tight sm:text-xl">
                  Engineering & Technical Specifications Sheet
                </h3>
                <p ui-card-description class="text-xs">
                  Comprehensive acoustic measurements, electrical parameters, material tolerances, and compliance standards.
                </p>
              </div>
    
              <div class="flex flex-wrap items-center gap-2.5">
                <!-- Metric / Imperial Toggle -->
                <div class="border-border bg-background flex items-center rounded-lg border p-0.5 text-xs shadow-2xs">
                  <button type="button" [class]="unitToggleClass(isMetric())" (click)="isMetric.set(true)">
                    Metric (SI)
                  </button>
                  <button type="button" [class]="unitToggleClass(!isMetric())" (click)="isMetric.set(false)">
                    Imperial (US)
                  </button>
                </div>
    
                <button ui-button variant="outline" size="sm" class="gap-1.5 text-xs shadow-2xs" (click)="downloadPdf()">
                  <lucide-icon [img]="FileDown" class="size-3.5" />
                  <span>Download Spec PDF</span>
                </button>
              </div>
            </div>
    
            <!-- Controls: Category Filter Tabs & Search Bar -->
            <div class="mt-4 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
              <div class="flex flex-wrap items-center gap-1.5">
                @for (cat of categoryOptions; track cat.id) {
                  <button type="button" [class]="categoryClass(cat.id)" (click)="activeCategory.set(cat.id)">
                    {{ cat.label }}
                  </button>
                }
              </div>
    
              <div class="relative w-full sm:w-64">
                <lucide-icon
                  [img]="Search"
                  class="text-muted-foreground absolute top-1/2 left-2.5 size-3.5 -translate-y-1/2"
                />
                <ui-input
                  [value]="searchQuery()"
                  (valueChange)="searchQuery.set($event)"
                  placeholder="Search parameters..."
                  class="h-8 pl-8 text-xs"
                />
              </div>
            </div>
          </div>
    
          <div ui-card-content class="p-0">
            <table ui-table>
              <thead ui-table-header>
                <tr ui-table-row class="bg-muted/40">
                  <th ui-table-head class="w-[280px]">Parameter / Measurement</th>
                  <th ui-table-head>Specification ({{ isMetric() ? 'Metric' : 'Imperial' }})</th>
                  <th ui-table-head class="w-[180px]">Tolerance / Range</th>
                  <th ui-table-head class="w-[220px]">Test Standard / Method</th>
                </tr>
              </thead>
              <tbody ui-table-body>
                @for (spec of filteredSpecs(); track spec.id) {
                  <tr ui-table-row [class]="spec.highlight ? 'bg-primary/5' : ''">
                    <td ui-table-cell>
                      <div class="flex items-center gap-2">
                        <span class="text-foreground text-xs font-medium">{{ spec.parameter }}</span>
                        @if (spec.highlight) {
                          <span ui-badge variant="secondary" class="h-4.5 px-1.5 font-mono text-xs font-semibold uppercase">
                            Key Spec
                          </span>
                        }
                      </div>
                    </td>
                    <td ui-table-cell>
                      <span class="text-foreground font-mono text-xs font-semibold">
                        {{ isMetric() ? spec.metricValue : spec.imperialValue }}
                      </span>
                    </td>
                    <td ui-table-cell>
                      <span class="text-muted-foreground font-mono text-xs">{{ spec.tolerance || '—' }}</span>
                    </td>
                    <td ui-table-cell>
                      <span class="text-muted-foreground text-xs">{{ spec.testCondition || 'Factory Standard Q/C' }}</span>
                    </td>
                  </tr>
                }
                @if (filteredSpecs().length === 0) {
                  <tr ui-table-row>
                    <td ui-table-cell [attr.colspan]="4" class="text-muted-foreground h-28 text-center text-xs">
                      No matching specifications found for "{{ searchQuery() }}".
                    </td>
                  </tr>
                }
              </tbody>
            </table>
    
            <!-- Bottom Compliance Certification Footer Badges -->
            <div class="border-border bg-muted/20 flex flex-wrap items-center justify-between gap-3 border-t p-4 text-xs">
              <div class="text-muted-foreground flex flex-wrap items-center gap-4">
                <div class="flex items-center gap-1.5">
                  <lucide-icon [img]="ShieldCheck" class="text-primary size-4" />
                  <span class="text-foreground font-medium">Calibrated Precision</span>
                </div>
                <div class="flex items-center gap-1.5">
                  <lucide-icon [img]="CheckCircle2" class="text-success size-4" />
                  <span>3-Year Studio Warranty Included</span>
                </div>
                <div class="flex items-center gap-1.5">
                  <lucide-icon [img]="Info" class="text-info size-4" />
                  <span>Serial matched drivers (± 0.3 dB stereo tracking)</span>
                </div>
              </div>
    
              <span class="text-muted-foreground font-mono text-xs"> Document ID: SPEC-APX-{{ modelNumber }}-2026 </span>
            </div>
          </div>
        </div>
      `,
    })
    export class UiTechnicalSpecsSheetComponent {
      protected readonly CheckCircle2 = CheckCircle2
      protected readonly FileDown = FileDown
      protected readonly Info = Info
      protected readonly Search = Search
      protected readonly ShieldCheck = ShieldCheck
    
      protected readonly categoryOptions = categories
    
      @Input() productTitle = 'Apex Pro Reference Planar-Magnetic Studio Monitor Headphones'
      @Input() modelNumber = 'APX-950-PRO'
      @Input() revision = 'Rev 2.4 (2026)'
      @Input() set specs(v: SpecItem[] | undefined) {
        this.specsSignal.set(v ?? defaultSpecs)
      }
      @Input('class') className?: string
    
      readonly specsSignal = signal<SpecItem[]>(defaultSpecs)
      readonly isMetric = signal(true)
      readonly activeCategory = signal<'all' | SpecCategory>('all')
      readonly searchQuery = signal('')
    
      readonly filteredSpecs = computed(() => {
        const cat = this.activeCategory()
        const q = this.searchQuery().trim().toLowerCase()
        return this.specsSignal().filter((item) => {
          const matchesCat = cat === 'all' || item.category === cat
          const matchesSearch =
            q === '' ||
            item.parameter.toLowerCase().includes(q) ||
            item.metricValue.toLowerCase().includes(q) ||
            item.imperialValue.toLowerCase().includes(q)
          return matchesCat && matchesSearch
        })
      })
    
      get rootClass(): string {
        return cn('border-border w-full shadow-xs', this.className)
      }
    
      unitToggleClass(active: boolean): string {
        return cn(
          'rounded-md px-2.5 py-1 font-medium transition-colors',
          active
            ? 'bg-primary text-primary-foreground font-semibold shadow-2xs'
            : 'text-muted-foreground hover:text-foreground',
        )
      }
    
      categoryClass(id: 'all' | SpecCategory): string {
        return cn(
          'rounded-md px-2.5 py-1 text-xs font-medium transition-colors',
          this.activeCategory() === id
            ? 'bg-foreground text-background shadow-2xs'
            : 'bg-muted/60 text-muted-foreground hover:bg-muted hover:text-foreground border border-transparent',
        )
      }
    
      downloadPdf(): void {
        alert(`Downloading official engineering data sheet for ${this.modelNumber} (${this.revision})...`)
      }
    }
    
  • src/app/components/blocks/technical-specs-sheet/index.ts0.1 kB

Raw manifest:https://uipkge.dev/r/angular/technical-specs-sheet.json