{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "integrations-search-directory",
  "title": "Integrations — Search Directory",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-angular/blocks/integrations-search-directory/integrations-search-directory.component.ts",
      "content": "// Searchable integration directory: a live filter field above category-grouped\n// rows carrying a monogram, blurb, and auth type, with an empty state when\n// nothing matches. Port of the React block 1:1.\nimport { ChangeDetectionStrategy, Component, Input, computed, signal } from '@angular/core'\nimport { ArrowUpRight, LucideAngularModule, Search } from 'lucide-angular'\nimport { cn } from '@/lib/utils'\nimport { UiBadgeComponent } from '@/ui/badge/badge.component'\nimport { UiButtonComponent } from '@/ui/button/button.component'\nimport { UiCardComponent, UiCardContentComponent } from '@/ui/card/card.component'\nimport { UiInputComponent } from '@/ui/input/input.component'\nimport { UiSeparatorComponent } from '@/ui/separator/separator.component'\n\ninterface DirectoryIntegration {\n  mark: string\n  name: string\n  category: string\n  blurb: string\n  auth: string\n}\n\nconst integrations: DirectoryIntegration[] = [\n  {\n    mark: 'SF',\n    name: 'Snowflake',\n    category: 'Warehouses',\n    blurb: 'Query live tables with a read-only role.',\n    auth: 'Key pair',\n  },\n  {\n    mark: 'BQ',\n    name: 'BigQuery',\n    category: 'Warehouses',\n    blurb: 'Service-account access, per-dataset scope.',\n    auth: 'Service account',\n  },\n  {\n    mark: 'RS',\n    name: 'Redshift',\n    category: 'Warehouses',\n    blurb: 'Serverless and provisioned clusters.',\n    auth: 'IAM role',\n  },\n  {\n    mark: 'OK',\n    name: 'Okta',\n    category: 'Identity',\n    blurb: 'Group membership drives row-level scope.',\n    auth: 'SAML + SCIM',\n  },\n  {\n    mark: 'EI',\n    name: 'Entra ID',\n    category: 'Identity',\n    blurb: 'Directory groups sync on a schedule.',\n    auth: 'SAML + SCIM',\n  },\n  {\n    mark: 'SL',\n    name: 'Slack',\n    category: 'Alerting',\n    blurb: 'Route alerts to the metric owner’s channel.',\n    auth: 'OAuth',\n  },\n  {\n    mark: 'PD',\n    name: 'PagerDuty',\n    category: 'Alerting',\n    blurb: 'Page on freshness breaches, not on noise.',\n    auth: 'API key',\n  },\n  {\n    mark: 'DT',\n    name: 'dbt',\n    category: 'Modelling',\n    blurb: 'Import models as certified metric sources.',\n    auth: 'API token',\n  },\n  {\n    mark: 'AF',\n    name: 'Airflow',\n    category: 'Modelling',\n    blurb: 'Trigger materialisation from your DAGs.',\n    auth: 'API token',\n  },\n]\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-integrations-search-directory, [ui-integrations-search-directory]',\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: [\n    LucideAngularModule,\n    UiBadgeComponent,\n    UiButtonComponent,\n    UiCardComponent,\n    UiCardContentComponent,\n    UiInputComponent,\n    UiSeparatorComponent,\n  ],\n  template: `\n    <section data-slot=\"integrations-search-directory\" [class]=\"rootClass\">\n      <div class=\"mx-auto max-w-4xl px-6 py-20 lg:py-28\">\n        <div class=\"flex flex-wrap items-end justify-between gap-6\">\n          <div>\n            <span ui-badge variant=\"secondary\">Directory</span>\n            <h2 class=\"mt-4 text-3xl font-semibold tracking-tight sm:text-4xl\">Find your stack</h2>\n          </div>\n\n          <div class=\"relative w-full sm:w-72\">\n            <ui-input\n              [value]=\"query()\"\n              (valueChange)=\"query.set($event)\"\n              class=\"pl-9\"\n              placeholder=\"Search integrations…\"\n              aria-label=\"Search integrations\"\n              [prefixIcon]=\"searchIcon\"\n            />\n            <ng-template #searchIcon>\n              <lucide-icon\n                [img]=\"Search\"\n                class=\"text-muted-foreground pointer-events-none absolute top-1/2 left-3 size-4 -translate-y-1/2\"\n              />\n            </ng-template>\n          </div>\n        </div>\n\n        <div ui-card class=\"mt-8\">\n          <div ui-card-content class=\"p-2\">\n            @if (groups().length === 0) {\n              <div class=\"px-4 py-16 text-center\">\n                <p class=\"text-sm font-medium\">No integration matches “{{ query() }}”.</p>\n                <p class=\"text-muted-foreground mt-1 text-xs\">\n                  We ship new connectors most releases — tell us which one you need.\n                </p>\n                <button ui-button variant=\"outline\" size=\"sm\" class=\"mt-4\">Request an integration</button>\n              </div>\n            }\n\n            @for (group of groups(); track group.category; let last = $last) {\n              <div>\n                <p class=\"text-muted-foreground px-4 pt-4 pb-2 font-mono text-xs tracking-[0.14em] uppercase\">\n                  {{ group.category }}\n                </p>\n                <ul>\n                  @for (integration of group.items; track integration.name) {\n                    <li>\n                      <a\n                        href=\"#\"\n                        (click)=\"$event.preventDefault()\"\n                        class=\"hover:bg-muted focus-visible:ring-ring group flex items-center gap-4 rounded-lg px-4 py-3 transition-colors focus-visible:ring-2 focus-visible:outline-none\"\n                      >\n                        <span\n                          class=\"border-border text-muted-foreground flex size-9 shrink-0 items-center justify-center rounded-md border font-mono text-xs font-semibold\"\n                          aria-hidden=\"true\"\n                        >\n                          {{ integration.mark }}\n                        </span>\n                        <span class=\"min-w-0 flex-1\">\n                          <span class=\"block text-sm font-medium\">{{ integration.name }}</span>\n                          <span class=\"text-muted-foreground block truncate text-xs\">{{ integration.blurb }}</span>\n                        </span>\n                        <span ui-badge variant=\"outline\" class=\"hidden shrink-0 sm:inline-flex\">\n                          {{ integration.auth }}\n                        </span>\n                        <lucide-icon\n                          [img]=\"ArrowUpRight\"\n                          class=\"text-muted-foreground size-4 shrink-0 transition-transform group-hover:translate-x-0.5 group-hover:-translate-y-0.5\"\n                          aria-hidden=\"true\"\n                        />\n                      </a>\n                    </li>\n                  }\n                </ul>\n                @if (!last) {\n                  <ui-separator class=\"my-2\" />\n                }\n              </div>\n            }\n          </div>\n        </div>\n      </div>\n    </section>\n  `,\n})\nexport class UiIntegrationsSearchDirectoryComponent {\n  protected readonly ArrowUpRight = ArrowUpRight\n  protected readonly Search = Search\n\n  @Input() set initialQuery(v: string) {\n    this.query.set(v ?? '')\n  }\n  @Input('class') className?: string\n\n  readonly query = signal('')\n\n  readonly groups = computed(() => {\n    const q = this.query().trim().toLowerCase()\n    const matches = q\n      ? integrations.filter((i) => `${i.name} ${i.category} ${i.blurb} ${i.auth}`.toLowerCase().includes(q))\n      : integrations\n    const ordered: { category: string; items: DirectoryIntegration[] }[] = []\n    const byCategory = new Map<string, DirectoryIntegration[]>()\n    for (const integration of matches) {\n      const list = byCategory.get(integration.category)\n      if (list) list.push(integration)\n      else {\n        const next: DirectoryIntegration[] = [integration]\n        byCategory.set(integration.category, next)\n        ordered.push({ category: integration.category, items: next })\n      }\n    }\n    return ordered\n  })\n\n  get rootClass(): string {\n    return cn('bg-background', this.className)\n  }\n}\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/integrations-search-directory/integrations-search-directory.component.ts"
    },
    {
      "path": "packages/registry-angular/blocks/integrations-search-directory/index.ts",
      "content": "export { UiIntegrationsSearchDirectoryComponent } from './integrations-search-directory.component'\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/integrations-search-directory/index.ts"
    }
  ],
  "dependencies": [
    "lucide-angular"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/angular/badge.json",
    "https://uipkge.dev/r/angular/button.json",
    "https://uipkge.dev/r/angular/card.json",
    "https://uipkge.dev/r/angular/input.json",
    "https://uipkge.dev/r/angular/separator.json"
  ],
  "description": "Searchable integration directory: a live filter field above category-grouped rows carrying a monogram, blurb, and auth type, with an empty state when nothing matches.",
  "categories": [
    "marketing"
  ]
}