{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "feature-accordion-media",
  "title": "Features — Accordion with Media",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-angular/blocks/feature-accordion-media/feature-accordion-media.component.ts",
      "content": "// Single-open accordion of features beside a media pane that follows the open item,\n// collapsing to inline panes on narrow screens where a side pane would be unreadable.\n// Port of the React block 1:1.\nimport { ChangeDetectionStrategy, Component, Input, computed, signal } from '@angular/core'\nimport { cn } from '@/lib/utils'\nimport {\n  UiAccordionComponent,\n  UiAccordionContentComponent,\n  UiAccordionItemComponent,\n  UiAccordionTriggerComponent,\n} from '@/ui/accordion/accordion.component'\nimport { UiBadgeComponent } from '@/ui/badge/badge.component'\nimport { UiButtonComponent } from '@/ui/button/button.component'\nimport { UiCardComponent, UiCardContentComponent } from '@/ui/card/card.component'\n\nexport interface AccordionMediaRow {\n  name: string\n  detail: string\n}\n\nexport interface AccordionMediaFeature {\n  id: string\n  title: string\n  body: string\n  rows: AccordionMediaRow[]\n}\n\nconst features: AccordionMediaFeature[] = [\n  {\n    id: 'catalog',\n    title: 'Catalogue what exists',\n    body: 'The schema mirror lists every table and column with its freshness, so modelling starts from what is actually there rather than what the docs claim.',\n    rows: [\n      { name: 'warehouse.orders', detail: 'fresh · 2 min' },\n      { name: 'warehouse.refunds', detail: 'fresh · 2 min' },\n      { name: 'legacy.orders_v1', detail: 'stale · 41 days' },\n    ],\n  },\n  {\n    id: 'review',\n    title: 'Review before it ships',\n    body: 'Definition changes open a pull request. Reviewers see the diff, the consumers affected, and the reconciliation result against prior quarters.',\n    rows: [\n      { name: '+ window: trailing_28d', detail: 'added' },\n      { name: '- window: trailing_30d', detail: 'removed' },\n      { name: '41 consumers affected', detail: 'checked' },\n    ],\n  },\n  {\n    id: 'observe',\n    title: 'Watch it after it ships',\n    body: 'Freshness and drift alerts fire against certified metrics and route to the owner named in the definition, not a shared inbox.',\n    rows: [\n      { name: 'revenue_net', detail: 'within threshold' },\n      { name: 'margin_by_channel', detail: 'within threshold' },\n      { name: 'pipeline_weighted', detail: 'drift · owner paged' },\n    ],\n  },\n]\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-feature-accordion-media, [ui-feature-accordion-media]',\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    UiAccordionComponent,\n    UiAccordionContentComponent,\n    UiAccordionItemComponent,\n    UiAccordionTriggerComponent,\n    UiBadgeComponent,\n    UiButtonComponent,\n    UiCardComponent,\n    UiCardContentComponent,\n  ],\n  template: `\n    <section data-slot=\"feature-accordion-media\" [class]=\"rootClass\">\n      <div class=\"mx-auto max-w-6xl px-6 py-20 lg:py-28\">\n        <div class=\"max-w-2xl\">\n          <span ui-badge variant=\"secondary\">Workflow</span>\n          <h2 class=\"mt-4 text-3xl font-semibold tracking-tight sm:text-4xl\">Three habits, one loop</h2>\n        </div>\n\n        <div class=\"mt-10 grid gap-8 lg:grid-cols-2 lg:gap-12\">\n          <ui-accordion type=\"single\" collapsible [value]=\"open()\" (valueChange)=\"setOpen($event)\" class=\"w-full\">\n            @for (feature of features; track feature.id) {\n              <ui-accordion-item [value]=\"feature.id\">\n                <button ui-accordion-trigger class=\"text-left font-medium\">{{ feature.title }}</button>\n                <ui-accordion-content class=\"text-muted-foreground leading-relaxed\">\n                  {{ feature.body }}\n                  <!-- Inline pane below the breakpoint, where a side pane would be\n                       too narrow to read. -->\n                  <div ui-card class=\"mt-4 lg:hidden\">\n                    <div ui-card-content class=\"p-0\">\n                      <ul class=\"divide-border divide-y\">\n                        @for (row of feature.rows; track row.name) {\n                          <li class=\"flex items-center justify-between gap-3 px-4 py-2.5\">\n                            <span class=\"text-foreground min-w-0 truncate font-mono text-xs\">{{ row.name }}</span>\n                            <span class=\"shrink-0 text-xs\">{{ row.detail }}</span>\n                          </li>\n                        }\n                      </ul>\n                    </div>\n                  </div>\n                </ui-accordion-content>\n              </ui-accordion-item>\n            }\n          </ui-accordion>\n\n          <div ui-card class=\"hidden self-start lg:block\">\n            <div ui-card-content class=\"p-0\">\n              <div class=\"border-border text-muted-foreground border-b px-4 py-2.5 font-mono text-xs\">\n                {{ active().title }}\n              </div>\n              <ul class=\"divide-border divide-y\">\n                @for (row of active().rows; track row.name) {\n                  <li class=\"flex items-center justify-between gap-3 px-4 py-3\">\n                    <span class=\"min-w-0 truncate font-mono text-xs\">{{ row.name }}</span>\n                    <span class=\"text-muted-foreground shrink-0 text-xs\">{{ row.detail }}</span>\n                  </li>\n                }\n              </ul>\n            </div>\n          </div>\n        </div>\n\n        <button ui-button variant=\"ghost\" class=\"mt-8\">See the full workflow</button>\n      </div>\n    </section>\n  `,\n})\nexport class UiFeatureAccordionMediaComponent {\n  protected readonly features = features\n\n  @Input('class') className?: string\n  @Input() set initialOpen(v: string | undefined) {\n    if (v) this.open.set(v)\n  }\n\n  readonly open = signal<string>(features[0]!.id)\n\n  // The pane follows the open item; falling back to the first keeps the pane\n  // populated when everything is collapsed.\n  readonly active = computed<AccordionMediaFeature>(\n    () => features.find((feature) => feature.id === this.open()) ?? features[0]!,\n  )\n\n  setOpen(value: string | string[]): void {\n    this.open.set(Array.isArray(value) ? (value[0] ?? '') : value)\n  }\n\n  get rootClass(): string {\n    return cn('bg-background', this.className)\n  }\n}\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/feature-accordion-media/feature-accordion-media.component.ts"
    },
    {
      "path": "packages/registry-angular/blocks/feature-accordion-media/index.ts",
      "content": "export {\n  UiFeatureAccordionMediaComponent,\n  type AccordionMediaFeature,\n  type AccordionMediaRow,\n} from './feature-accordion-media.component'\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/feature-accordion-media/index.ts"
    }
  ],
  "dependencies": [
    "lucide-angular"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/angular/accordion.json",
    "https://uipkge.dev/r/angular/badge.json",
    "https://uipkge.dev/r/angular/button.json",
    "https://uipkge.dev/r/angular/card.json"
  ],
  "description": "Single-open accordion of features beside a media pane that follows the open item, collapsing to inline panes on narrow screens where a side pane would be unreadable.",
  "categories": [
    "marketing"
  ]
}