{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "feature-matrix-table-dense",
  "title": "Feature Matrix Table Dense",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-angular/blocks/feature-matrix-table-dense/feature-matrix-table-dense.component.ts",
      "content": "// Dense Linear-style technical specification matrix: collapsible category sections,\n// capability search, and a \"differences only\" toggle across UIPKGE / NPM monolith /\n// in-house columns. Port of the React block 1:1.\nimport { ChangeDetectionStrategy, Component, Input, computed, signal } from '@angular/core'\nimport {\n  ArrowRight,\n  Check,\n  CheckCircle2,\n  ChevronDown,\n  ChevronRight,\n  Filter,\n  LucideAngularModule,\n  Minus,\n  Search,\n} from 'lucide-angular'\nimport { cn } from '@/lib/utils'\n\nexport interface MatrixFeature {\n  name: string\n  description: string\n  uipkge: boolean | string\n  npmMonolith: boolean | string\n  inHouse: boolean | string\n}\n\nexport interface MatrixCategory {\n  id: string\n  title: string\n  features: MatrixFeature[]\n}\n\nconst DEFAULT_CATEGORIES: MatrixCategory[] = [\n  {\n    id: 'architecture',\n    title: 'Architecture & Runtime Ownership',\n    features: [\n      {\n        name: 'Zero NPM Runtime Dependency',\n        description: 'Source code copied directly into your repository with 100% ownership.',\n        uipkge: true,\n        npmMonolith: false,\n        inHouse: true,\n      },\n      {\n        name: 'Dual Framework 1:1 Parity',\n        description: 'Synchronized Vue 3.5 and React 19 primitives with identical token bindings.',\n        uipkge: true,\n        npmMonolith: false,\n        inHouse: 'High Cost',\n      },\n      {\n        name: 'Deterministic Tree-Shaking',\n        description: 'Zero dead code in final production bundles; only imported components compile.',\n        uipkge: true,\n        npmMonolith: 'Partial',\n        inHouse: true,\n      },\n      {\n        name: 'Tailwind CSS v4 Native Tokens',\n        description: 'Direct @theme inline OKLCH bindings without legacy tailwind.config.js overhead.',\n        uipkge: true,\n        npmMonolith: false,\n        inHouse: 'Manual',\n      },\n    ],\n  },\n  {\n    id: 'developer-experience',\n    title: 'Developer Experience & Tooling',\n    features: [\n      {\n        name: 'Headless Primitive Foundation',\n        description: 'Powered by battle-tested Reka UI and Radix UI accessible state machines.',\n        uipkge: true,\n        npmMonolith: 'Proprietary',\n        inHouse: 'Custom',\n      },\n      {\n        name: 'CLI Direct Add Workflow',\n        description: 'Install via npx shadcn add @uipkge/<name> with automated dependency resolution.',\n        uipkge: true,\n        npmMonolith: false,\n        inHouse: false,\n      },\n      {\n        name: 'Automated Parity Check Suite',\n        description: 'Built-in AST parity verification scripts guarding against framework drift.',\n        uipkge: true,\n        npmMonolith: false,\n        inHouse: false,\n      },\n    ],\n  },\n  {\n    id: 'enterprise',\n    title: 'Enterprise & Security Compliance',\n    features: [\n      {\n        name: 'Zero Supply-Chain Vulnerabilities',\n        description: 'No third-party registry packages or compromised transitive semver updates.',\n        uipkge: true,\n        npmMonolith: false,\n        inHouse: true,\n      },\n      {\n        name: 'Full Customization Freedom',\n        description: 'Modify any CSS class, DOM attribute, or internal logic without breaking upstream.',\n        uipkge: true,\n        npmMonolith: 'Limited',\n        inHouse: true,\n      },\n      {\n        name: 'WCAG 2.1 AA Accessibility',\n        description: 'Keyboard ergonomics, focus rings, ARIA roles, and screen-reader audited.',\n        uipkge: true,\n        npmMonolith: 'Partial',\n        inHouse: 'Manual',\n      },\n    ],\n  },\n]\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-feature-matrix-table-dense, [ui-feature-matrix-table-dense]',\n  standalone: true,\n  // React renders the root <section> itself: the host stays out of layout and `class` goes to the root.\n  host: { '[attr.class]': '\"contents\"' },\n  imports: [LucideAngularModule],\n  template: `\n    <section data-slot=\"feature-matrix-table-dense\" [class]=\"rootClass\">\n      <div class=\"mx-auto max-w-7xl px-4 sm:px-6 lg:px-8\">\n        <!-- Section Header -->\n        <div class=\"mx-auto max-w-3xl space-y-4 text-center\">\n          <a\n            href=\"#technical-matrix\"\n            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\"\n          >\n            <lucide-icon [img]=\"CheckCircle2\" class=\"text-primary size-3.5\" />\n            <span>Technical Architecture Comparison</span>\n            <lucide-icon\n              [img]=\"ArrowRight\"\n              class=\"text-muted-foreground size-3 transition-transform group-hover:translate-x-0.5\"\n            />\n          </a>\n\n          <h2 class=\"text-foreground text-3xl font-bold tracking-tight sm:text-4xl\">\n            {{ title }}\n          </h2>\n\n          <p class=\"text-muted-foreground text-base sm:text-lg\">\n            {{ description }}\n          </p>\n        </div>\n\n        <!-- Controls Strip: Search & Filter Toggles -->\n        <div\n          class=\"border-border mt-10 flex flex-col gap-3 border-b pb-4 sm:flex-row sm:items-center sm:justify-between\"\n        >\n          <div class=\"relative w-full sm:w-72\">\n            <lucide-icon\n              [img]=\"Search\"\n              class=\"text-muted-foreground absolute top-1/2 left-2.5 size-3.5 -translate-y-1/2\"\n            />\n            <input\n              type=\"text\"\n              placeholder=\"Filter capabilities...\"\n              [value]=\"searchQuery()\"\n              (input)=\"searchQuery.set($any($event.target).value)\"\n              class=\"border-border bg-muted/40 text-foreground placeholder:text-muted-foreground focus-visible:ring-primary h-8 w-full rounded-md border pr-3 pl-8 text-xs focus-visible:ring-1 focus-visible:outline-none\"\n            />\n          </div>\n\n          <div class=\"flex items-center gap-2\">\n            <button type=\"button\" [class]=\"differencesButtonClass\" (click)=\"onlyDifferences.set(!onlyDifferences())\">\n              <lucide-icon [img]=\"Filter\" class=\"size-3\" />\n              <span>Show Differences Only</span>\n            </button>\n          </div>\n        </div>\n\n        <!-- Dense Comparison Matrix Table -->\n        <div class=\"border-border bg-card mt-6 overflow-x-auto rounded-lg border shadow-xs\">\n          <table class=\"w-full text-left text-xs\">\n            <!-- Table Sticky Header -->\n            <thead class=\"border-border bg-muted/50 text-foreground border-b font-medium\">\n              <tr>\n                <th scope=\"col\" class=\"w-1/2 py-3.5 pr-3 pl-4 text-sm font-semibold sm:w-5/12 sm:pl-6\">\n                  Capability & Specification\n                </th>\n                <th\n                  scope=\"col\"\n                  class=\"text-primary bg-primary/5 border-border border-x px-3 py-3.5 text-center font-bold sm:w-3/12\"\n                >\n                  <div class=\"flex flex-col items-center\">\n                    <span>UIPKGE Registry</span>\n                    <span class=\"text-muted-foreground text-xs font-normal\">Unbundled Code Ownership</span>\n                  </div>\n                </th>\n                <th scope=\"col\" class=\"text-muted-foreground px-3 py-3.5 text-center sm:w-2/12\">\n                  <div class=\"flex flex-col items-center\">\n                    <span>NPM Monolith</span>\n                    <span class=\"text-muted-foreground text-xs font-normal\">e.g. MUI / AntD</span>\n                  </div>\n                </th>\n                <th scope=\"col\" class=\"text-muted-foreground px-3 py-3.5 text-center sm:w-2/12\">\n                  <div class=\"flex flex-col items-center\">\n                    <span>In-House Bespoke</span>\n                    <span class=\"text-muted-foreground text-xs font-normal\">Built from scratch</span>\n                  </div>\n                </th>\n              </tr>\n            </thead>\n\n            <!-- Table Body with Collapsible Category Rows -->\n            <tbody class=\"divide-border/60 divide-y\">\n              @for (category of filteredCategories(); track category.id) {\n                <!-- Category Header Row -->\n                <tr\n                  class=\"bg-muted/30 hover:bg-muted/50 cursor-pointer transition-colors select-none\"\n                  (click)=\"toggleCategory(category.id)\"\n                >\n                  <td colspan=\"4\" class=\"py-2.5 pr-3 pl-4 sm:pl-6\">\n                    <div class=\"text-foreground flex items-center gap-1.5 text-xs font-bold tracking-wider uppercase\">\n                      @if (!collapsedCategories()[category.id]) {\n                        <lucide-icon [img]=\"ChevronDown\" class=\"text-muted-foreground size-3.5\" />\n                      } @else {\n                        <lucide-icon [img]=\"ChevronRight\" class=\"text-muted-foreground size-3.5\" />\n                      }\n                      <span>{{ category.title }}</span>\n                      <span class=\"text-muted-foreground ml-1 text-xs font-normal\">\n                        ({{ category.features.length }})\n                      </span>\n                    </div>\n                  </td>\n                </tr>\n\n                <!-- Feature Items Rows -->\n                @if (!collapsedCategories()[category.id]) {\n                  @for (feature of category.features; track feature.name) {\n                    <tr class=\"hover:bg-muted/10 transition-colors\">\n                      <!-- Feature Info (Col 1) -->\n                      <td class=\"space-y-0.5 py-3 pr-3 pl-4 sm:pl-6\">\n                        <div class=\"text-foreground font-semibold\">{{ feature.name }}</div>\n                        <div class=\"text-muted-foreground text-xs\">{{ feature.description }}</div>\n                      </td>\n\n                      <!-- UIPKGE (Col 2 - Highlighted) -->\n                      <td class=\"bg-primary/5 border-border border-x px-3 py-3 text-center\">\n                        <div class=\"flex items-center justify-center\">\n                          @if (feature.uipkge === true) {\n                            <span\n                              class=\"bg-success/10 text-success inline-flex size-5 items-center justify-center rounded-full font-bold\"\n                            >\n                              <lucide-icon [img]=\"Check\" class=\"size-3.5 stroke-[2.5]\" />\n                            </span>\n                          } @else if (typeof feature.uipkge === 'string') {\n                            <span class=\"bg-muted text-foreground rounded px-1.5 py-0.5 font-mono text-xs font-medium\">\n                              {{ feature.uipkge }}\n                            </span>\n                          } @else {\n                            <span class=\"text-muted-foreground\">\n                              <lucide-icon [img]=\"Minus\" class=\"size-3.5\" />\n                            </span>\n                          }\n                        </div>\n                      </td>\n\n                      <!-- NPM Monolith (Col 3) -->\n                      <td class=\"px-3 py-3 text-center\">\n                        <div class=\"flex items-center justify-center\">\n                          @if (feature.npmMonolith === true) {\n                            <span\n                              class=\"bg-success/10 text-success inline-flex size-5 items-center justify-center rounded-full\"\n                            >\n                              <lucide-icon [img]=\"Check\" class=\"size-3.5\" />\n                            </span>\n                          } @else if (typeof feature.npmMonolith === 'string') {\n                            <span\n                              class=\"bg-muted/60 text-muted-foreground rounded px-1.5 py-0.5 font-mono text-xs font-medium\"\n                            >\n                              {{ feature.npmMonolith }}\n                            </span>\n                          } @else {\n                            <span class=\"text-muted-foreground/60\">\n                              <lucide-icon [img]=\"Minus\" class=\"size-3.5\" />\n                            </span>\n                          }\n                        </div>\n                      </td>\n\n                      <!-- In-House Bespoke (Col 4) -->\n                      <td class=\"px-3 py-3 text-center\">\n                        <div class=\"flex items-center justify-center\">\n                          @if (feature.inHouse === true) {\n                            <span\n                              class=\"bg-success/10 text-success inline-flex size-5 items-center justify-center rounded-full\"\n                            >\n                              <lucide-icon [img]=\"Check\" class=\"size-3.5\" />\n                            </span>\n                          } @else if (typeof feature.inHouse === 'string') {\n                            <span\n                              class=\"bg-muted/60 text-muted-foreground rounded px-1.5 py-0.5 font-mono text-xs font-medium\"\n                            >\n                              {{ feature.inHouse }}\n                            </span>\n                          } @else {\n                            <span class=\"text-muted-foreground/60\">\n                              <lucide-icon [img]=\"Minus\" class=\"size-3.5\" />\n                            </span>\n                          }\n                        </div>\n                      </td>\n                    </tr>\n                  }\n                }\n              }\n            </tbody>\n          </table>\n        </div>\n      </div>\n    </section>\n  `,\n})\nexport class UiFeatureMatrixTableDenseComponent {\n  protected readonly ArrowRight = ArrowRight\n  protected readonly Check = Check\n  protected readonly CheckCircle2 = CheckCircle2\n  protected readonly ChevronDown = ChevronDown\n  protected readonly ChevronRight = ChevronRight\n  protected readonly Filter = Filter\n  protected readonly Minus = Minus\n  protected readonly Search = Search\n\n  @Input() title = 'Engineered for teams who demand complete source code control.'\n  @Input() description =\n    'Compare how unbundled registry components stack up against traditional monolithic npm packages and bespoke in-house design systems.'\n  @Input() set categories(v: MatrixCategory[] | undefined) {\n    this.customCategories.set(v ? v.map((c) => ({ ...c, features: [...c.features] })) : undefined)\n  }\n  @Input('class') className?: string\n\n  private readonly customCategories = signal<MatrixCategory[] | undefined>(undefined)\n\n  readonly searchQuery = signal('')\n  readonly onlyDifferences = signal(false)\n  readonly collapsedCategories = signal<Record<string, boolean>>({})\n\n  readonly activeCategories = computed(() => this.customCategories() ?? DEFAULT_CATEGORIES)\n\n  readonly filteredCategories = computed(() => {\n    const query = this.searchQuery().toLowerCase().trim()\n    const onlyDiff = this.onlyDifferences()\n    return this.activeCategories()\n      .map((cat) => ({\n        ...cat,\n        features: cat.features.filter((f) => {\n          const matchesQuery =\n            !query || f.name.toLowerCase().includes(query) || f.description.toLowerCase().includes(query)\n          if (!matchesQuery) return false\n          if (onlyDiff) {\n            const isSame = f.uipkge === f.npmMonolith && f.npmMonolith === f.inHouse\n            if (isSame) return false\n          }\n          return true\n        }),\n      }))\n      .filter((cat) => cat.features.length > 0)\n  })\n\n  get rootClass(): string {\n    return cn('bg-background relative overflow-hidden py-16 sm:py-24', this.className)\n  }\n\n  get differencesButtonClass(): string {\n    return cn(\n      'flex items-center gap-1.5 rounded-md border px-2.5 py-1.5 text-xs font-medium transition-colors',\n      this.onlyDifferences()\n        ? 'border-primary bg-primary/10 text-primary'\n        : 'border-border bg-card text-muted-foreground hover:text-foreground',\n    )\n  }\n\n  toggleCategory(catId: string): void {\n    this.collapsedCategories.update((prev) => ({ ...prev, [catId]: !prev[catId] }))\n  }\n}\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/feature-matrix-table-dense/feature-matrix-table-dense.component.ts"
    },
    {
      "path": "packages/registry-angular/blocks/feature-matrix-table-dense/index.ts",
      "content": "export {\n  UiFeatureMatrixTableDenseComponent,\n  type MatrixFeature,\n  type MatrixCategory,\n} from './feature-matrix-table-dense.component'\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/feature-matrix-table-dense/index.ts"
    }
  ],
  "dependencies": [
    "lucide-angular"
  ],
  "devDependencies": [],
  "registryDependencies": [],
  "description": "Dense Linear-style technical specification matrix table with collapsible sections and search filtering.",
  "categories": [
    "feature",
    "marketing"
  ]
}