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 shadcn@latest add https://uipkge.dev/r/lit/technical-specs-sheet.json
Manifest:https://uipkge.dev/r/lit/technical-specs-sheet.jsonInstalls to:src/uip/blocks/technical-specs-sheet/

Variants

Loading interactive previews…

Props

NameType / ValuesDefaultRequired
model-numberstringAPX-950-PROoptional
revisionstringRev 2.4 (2026)optional

Schema

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

SpecItem
interface SpecItem {
  id: string
  category: 'acoustic' | 'electrical' | 'physical' | 'connectivity' | 'compliance'
  parameter: string
  metricValue: string
  imperialValue: string
  tolerance?: string
  testCondition?: string
  highlight?: boolean
}
HTMLElementTagNameMap
interface HTMLElementTagNameMap {
    'uip-technical-specs-sheet': UipTechnicalSpecsSheet
  }
}

Files installed (1)

  • src/uip/blocks/technical-specs-sheet/technical-specs-sheet.ts17.3 kB
    import { LitElement, css, html, nothing } from 'lit'
    import { FileDown, ShieldCheck, CheckCircle2, Search, Info } from 'lucide'
    import { tailwind } from '../../lib/styles'
    import { ThemeController } from '../../lib/theme'
    import { icon } from '../../lib/icon'
    // Side-effect imports: register the composed elements for standalone use
    // (consumers who install only this block still get working primitives).
    import '../../components/badge/badge'
    import '../../components/button/button'
    import '../../components/card/card'
    import '../../components/input/input'
    import type { UipInput } from '../../components/input/input'
    
    export interface SpecItem {
      id: string
      category: 'acoustic' | 'electrical' | 'physical' | 'connectivity' | 'compliance'
      parameter: string
      metricValue: string
      imperialValue: string
      tolerance?: string
      testCondition?: string
      highlight?: boolean
    }
    
    const defaultSpecs: SpecItem[] = [
      {
        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',
      },
      {
        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 Ω',
      },
      {
        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',
      },
      {
        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',
      },
      {
        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)',
      },
    ]
    
    const categories = [
      { 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' },
    ]
    
    /**
     * <uip-technical-specs-sheet> — the registry TechnicalSpecsSheet block as a web component.
     *
     * Class strings and spec data are React's verbatim. React state becomes
     * { state: true } fields; `specs` is a property (attribute: false — set it from
     * JS). React's `downloadPdf` alert becomes a `download-pdf` CustomEvent
     * (bubbles, composed). The spec grid uses a native <table> with the registry
     * Table part classes copied verbatim — <uip-table>'s data-driven API can't
     * express per-row highlight classes or the empty-state spanning row. The
     * search icon moves from an absolute overlay into <uip-input>'s prefix slot.
     */
    export class UipTechnicalSpecsSheet extends LitElement {
      static shadowRootOptions = { ...LitElement.shadowRootOptions, delegatesFocus: true }
      // :host display can't be a utility class (the host has no template of its own).
      static styles = [tailwind, css`:host { display: block; }`]
    
      static properties = {
        modelNumber: { attribute: 'model-number' },
        revision: {},
        specs: { attribute: false },
        isMetric: { state: true },
        activeCategory: { state: true },
        searchQuery: { state: true },
      }
    
      modelNumber = 'APX-950-PRO'
      revision = 'Rev 2.4 (2026)'
      specs: SpecItem[] = defaultSpecs
    
      private isMetric = true
      private activeCategory = 'all'
      private searchQuery = ''
    
      constructor() {
        super()
        new ThemeController(this)
      }
    
      connectedCallback() {
        super.connectedCallback()
        this.setAttribute('data-uipkge', '')
        this.setAttribute('data-slot', 'technical-specs-sheet')
      }
    
      private get filteredSpecs(): SpecItem[] {
        return (this.specs ?? []).filter((item) => {
          const matchesCat = this.activeCategory === 'all' || item.category === this.activeCategory
          const matchesSearch =
            this.searchQuery.trim() === '' ||
            item.parameter.toLowerCase().includes(this.searchQuery.toLowerCase()) ||
            item.metricValue.toLowerCase().includes(this.searchQuery.toLowerCase()) ||
            item.imperialValue.toLowerCase().includes(this.searchQuery.toLowerCase())
          return matchesCat && matchesSearch
        })
      }
    
      render() {
        const filteredSpecs = this.filteredSpecs
        // data-slot lives on this wrapper: <uip-card> stamps its own
        // data-slot="card" on connect, which would clobber the block slot.
        return html`
          <div data-slot="technical-specs-sheet">
            <uip-card class="border-border w-full shadow-xs">
              <uip-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">
                        ${this.modelNumber} · ${this.revision}
                      </span>
                      <uip-badge variant="outline" class="text-success font-mono text-xs">Laboratory Certified</uip-badge>
                    </div>
                    <uip-card-title class="text-foreground text-lg font-semibold tracking-tight sm:text-xl">
                      Engineering & Technical Specifications Sheet
                    </uip-card-title>
                    <uip-card-description class="text-xs">
                      Comprehensive acoustic measurements, electrical parameters, material tolerances, and compliance standards.
                    </uip-card-description>
                  </div>
    
                  <div class="flex flex-wrap items-center gap-2.5">
                    <div class="border-border bg-background flex items-center rounded-lg border p-0.5 text-xs shadow-2xs">
                      <button
                        type="button"
                        class=${`rounded-md px-2.5 py-1 font-medium transition-colors ${
                          this.isMetric
                            ? 'bg-primary text-primary-foreground font-semibold shadow-2xs'
                            : 'text-muted-foreground hover:text-foreground'
                        }`}
                        @click=${() => {
                          this.isMetric = true
                        }}
                      >
                        Metric (SI)
                      </button>
                      <button
                        type="button"
                        class=${`rounded-md px-2.5 py-1 font-medium transition-colors ${
                          !this.isMetric
                            ? 'bg-primary text-primary-foreground font-semibold shadow-2xs'
                            : 'text-muted-foreground hover:text-foreground'
                        }`}
                        @click=${() => {
                          this.isMetric = false
                        }}
                      >
                        Imperial (US)
                      </button>
                    </div>
                    <uip-button
                      variant="outline"
                      size="sm"
                      class="gap-1.5 text-xs shadow-2xs"
                      @click=${() => this.dispatchEvent(new CustomEvent('download-pdf', { bubbles: true, composed: true }))}
                    >
                      ${icon(FileDown, 'file-down', 'size-3.5')}
                      <span>Download Spec PDF</span>
                    </uip-button>
                  </div>
                </div>
    
                <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">
                    ${categories.map(
                      (cat) => html`<button
                        type="button"
                        class=${`rounded-md px-2.5 py-1 text-xs font-medium transition-colors ${
                          this.activeCategory === cat.id
                            ? 'bg-foreground text-background shadow-2xs'
                            : 'bg-muted/60 text-muted-foreground hover:bg-muted hover:text-foreground border border-transparent'
                        }`}
                        @click=${() => {
                          this.activeCategory = cat.id
                        }}
                      >
                        ${cat.label}
                      </button>`,
                    )}
                  </div>
                  <uip-input
                    .value=${this.searchQuery}
                    placeholder="Search parameters..."
                    class="h-8 w-full text-xs sm:w-64"
                    @input=${(e: Event) => {
                      this.searchQuery = (e.target as UipInput).value ?? ''
                    }}
                  >
                    <span slot="prefix">${icon(Search, 'search', 'size-3.5')}</span>
                  </uip-input>
                </div>
              </uip-card-header>
    
              <uip-card-content class="p-0">
                <div data-uipkge="" data-slot="table-container" class="relative w-full overflow-auto">
                  <table data-uipkge="" data-slot="table" class="w-full caption-bottom text-sm">
                    <thead data-uipkge="" data-slot="table-header" class="bg-muted/50 [&_tr]:border-b">
                      <tr data-uipkge="" data-slot="table-row" class="border-b transition-colors duration-150 hover:bg-transparent">
                        <th
                          data-uipkge=""
                          data-slot="table-head"
                          class="text-foreground h-10 px-3 text-left align-middle text-sm font-medium whitespace-nowrap w-[280px]"
                        >
                          Parameter / Measurement
                        </th>
                        <th
                          data-uipkge=""
                          data-slot="table-head"
                          class="text-foreground h-10 px-3 text-left align-middle text-sm font-medium whitespace-nowrap"
                        >
                          Specification (${this.isMetric ? 'Metric' : 'Imperial'})
                        </th>
                        <th
                          data-uipkge=""
                          data-slot="table-head"
                          class="text-foreground h-10 px-3 text-left align-middle text-sm font-medium whitespace-nowrap w-[180px]"
                        >
                          Tolerance / Range
                        </th>
                        <th
                          data-uipkge=""
                          data-slot="table-head"
                          class="text-foreground h-10 px-3 text-left align-middle text-sm font-medium whitespace-nowrap w-[220px]"
                        >
                          Test Standard / Method
                        </th>
                      </tr>
                    </thead>
                    <tbody data-uipkge="" data-slot="table-body" class="[&_tr:last-child]:border-0">
                      ${filteredSpecs.map(
                        (spec) => html`<tr
                          data-uipkge=""
                          data-slot="table-row"
                          class=${`border-b transition-colors duration-150 hover:bg-muted/50 ${spec.highlight ? 'bg-primary/5' : ''}`}
                        >
                          <td data-uipkge="" data-slot="table-cell" class="px-3 py-2 align-middle text-sm whitespace-nowrap">
                            <div class="flex items-center gap-2">
                              <span class="text-foreground text-xs font-medium">${spec.parameter}</span>
                              ${spec.highlight
                                ? html`<uip-badge variant="secondary" class="h-4.5 px-1.5 font-mono text-xs font-semibold uppercase">
                                    Key Spec
                                  </uip-badge>`
                                : nothing}
                            </div>
                          </td>
                          <td data-uipkge="" data-slot="table-cell" class="px-3 py-2 align-middle text-sm whitespace-nowrap">
                            <span class="text-foreground font-mono text-xs font-semibold"
                              >${this.isMetric ? spec.metricValue : spec.imperialValue}</span
                            >
                          </td>
                          <td data-uipkge="" data-slot="table-cell" class="px-3 py-2 align-middle text-sm whitespace-nowrap">
                            <span class="text-muted-foreground font-mono text-xs">${spec.tolerance || '—'}</span>
                          </td>
                          <td data-uipkge="" data-slot="table-cell" class="px-3 py-2 align-middle text-sm whitespace-nowrap">
                            <span class="text-muted-foreground text-xs">${spec.testCondition || 'Factory Standard Q/C'}</span>
                          </td>
                        </tr>`,
                      )}
                      ${filteredSpecs.length === 0
                        ? html`<tr data-uipkge="" data-slot="table-row" class="border-b transition-colors duration-150">
                            <td
                              data-uipkge=""
                              data-slot="table-cell"
                              colSpan="4"
                              class="px-3 py-2 align-middle text-sm whitespace-nowrap text-muted-foreground h-28 text-center text-xs"
                            >
                              No matching specifications found for &quot;${this.searchQuery}&quot;.
                            </td>
                          </tr>`
                        : nothing}
                    </tbody>
                  </table>
                </div>
    
                <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">
                      ${icon(ShieldCheck, 'shield-check', 'text-primary size-4')}
                      <span class="text-foreground font-medium">Calibrated Precision</span>
                    </div>
                    <div class="flex items-center gap-1.5">
                      ${icon(CheckCircle2, 'circle-check', 'text-success size-4')}
                      <span>3-Year Studio Warranty Included</span>
                    </div>
                    <div class="flex items-center gap-1.5">
                      ${icon(Info, 'info', '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-${this.modelNumber}-2026</span>
                </div>
              </uip-card-content>
            </uip-card>
          </div>
        `
      }
    }
    
    customElements.get('uip-technical-specs-sheet') || customElements.define('uip-technical-specs-sheet', UipTechnicalSpecsSheet)
    
    declare global {
      interface HTMLElementTagNameMap {
        'uip-technical-specs-sheet': UipTechnicalSpecsSheet
      }
    }
    

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