Features — Accordion with Media
blockmarketingSingle-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.
Installation
$pnpm dlx uipkge-ng@latest add feature-accordion-media$npx uipkge-ng@latest add feature-accordion-media$yarn dlx uipkge-ng@latest add feature-accordion-media$bunx uipkge-ng@latest add feature-accordion-mediaManifest:
https://uipkge.dev/r/angular/feature-accordion-media.jsonInstalls to:src/app/components/blocks/feature-accordion-media/Variants
Loading interactive previews…
Loading interactive previews…
Schema
Type aliases exported from this item's source. Use these to shape the data you pass in.
AccordionMediaRowinterface AccordionMediaRow {
name: string
detail: string
}AccordionMediaFeatureinterface AccordionMediaFeature {
id: string
title: string
body: string
rows: AccordionMediaRow[]
}npm dependencies
Files installed (2)
src/app/components/blocks/feature-accordion-media/feature-accordion-media.component.ts6 kB
// 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. // Port of the React block 1:1. import { ChangeDetectionStrategy, Component, Input, computed, signal } from '@angular/core' import { cn } from '@/lib/utils' import { UiAccordionComponent, UiAccordionContentComponent, UiAccordionItemComponent, UiAccordionTriggerComponent, } from '@/ui/accordion/accordion.component' import { UiBadgeComponent } from '@/ui/badge/badge.component' import { UiButtonComponent } from '@/ui/button/button.component' import { UiCardComponent, UiCardContentComponent } from '@/ui/card/card.component' export interface AccordionMediaRow { name: string detail: string } export interface AccordionMediaFeature { id: string title: string body: string rows: AccordionMediaRow[] } const features: AccordionMediaFeature[] = [ { id: 'catalog', title: 'Catalogue what exists', 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.', rows: [ { name: 'warehouse.orders', detail: 'fresh · 2 min' }, { name: 'warehouse.refunds', detail: 'fresh · 2 min' }, { name: 'legacy.orders_v1', detail: 'stale · 41 days' }, ], }, { id: 'review', title: 'Review before it ships', body: 'Definition changes open a pull request. Reviewers see the diff, the consumers affected, and the reconciliation result against prior quarters.', rows: [ { name: '+ window: trailing_28d', detail: 'added' }, { name: '- window: trailing_30d', detail: 'removed' }, { name: '41 consumers affected', detail: 'checked' }, ], }, { id: 'observe', title: 'Watch it after it ships', body: 'Freshness and drift alerts fire against certified metrics and route to the owner named in the definition, not a shared inbox.', rows: [ { name: 'revenue_net', detail: 'within threshold' }, { name: 'margin_by_channel', detail: 'within threshold' }, { name: 'pipeline_weighted', detail: 'drift · owner paged' }, ], }, ] @Component({ changeDetection: ChangeDetectionStrategy.Eager, selector: 'ui-feature-accordion-media, [ui-feature-accordion-media]', standalone: true, // React renders the root <section> itself: the host stays out of layout and `class` goes to the root. host: { '[attr.class]': '"contents"' }, imports: [ UiAccordionComponent, UiAccordionContentComponent, UiAccordionItemComponent, UiAccordionTriggerComponent, UiBadgeComponent, UiButtonComponent, UiCardComponent, UiCardContentComponent, ], template: ` <section data-slot="feature-accordion-media" [class]="rootClass"> <div class="mx-auto max-w-6xl px-6 py-20 lg:py-28"> <div class="max-w-2xl"> <span ui-badge variant="secondary">Workflow</span> <h2 class="mt-4 text-3xl font-semibold tracking-tight sm:text-4xl">Three habits, one loop</h2> </div> <div class="mt-10 grid gap-8 lg:grid-cols-2 lg:gap-12"> <ui-accordion type="single" collapsible [value]="open()" (valueChange)="setOpen($event)" class="w-full"> @for (feature of features; track feature.id) { <ui-accordion-item [value]="feature.id"> <button ui-accordion-trigger class="text-left font-medium">{{ feature.title }}</button> <ui-accordion-content class="text-muted-foreground leading-relaxed"> {{ feature.body }} <!-- Inline pane below the breakpoint, where a side pane would be too narrow to read. --> <div ui-card class="mt-4 lg:hidden"> <div ui-card-content class="p-0"> <ul class="divide-border divide-y"> @for (row of feature.rows; track row.name) { <li class="flex items-center justify-between gap-3 px-4 py-2.5"> <span class="text-foreground min-w-0 truncate font-mono text-xs">{{ row.name }}</span> <span class="shrink-0 text-xs">{{ row.detail }}</span> </li> } </ul> </div> </div> </ui-accordion-content> </ui-accordion-item> } </ui-accordion> <div ui-card class="hidden self-start lg:block"> <div ui-card-content class="p-0"> <div class="border-border text-muted-foreground border-b px-4 py-2.5 font-mono text-xs"> {{ active().title }} </div> <ul class="divide-border divide-y"> @for (row of active().rows; track row.name) { <li class="flex items-center justify-between gap-3 px-4 py-3"> <span class="min-w-0 truncate font-mono text-xs">{{ row.name }}</span> <span class="text-muted-foreground shrink-0 text-xs">{{ row.detail }}</span> </li> } </ul> </div> </div> </div> <button ui-button variant="ghost" class="mt-8">See the full workflow</button> </div> </section> `, }) export class UiFeatureAccordionMediaComponent { protected readonly features = features @Input('class') className?: string @Input() set initialOpen(v: string | undefined) { if (v) this.open.set(v) } readonly open = signal<string>(features[0]!.id) // The pane follows the open item; falling back to the first keeps the pane // populated when everything is collapsed. readonly active = computed<AccordionMediaFeature>( () => features.find((feature) => feature.id === this.open()) ?? features[0]!, ) setOpen(value: string | string[]): void { this.open.set(Array.isArray(value) ? (value[0] ?? '') : value) } get rootClass(): string { return cn('bg-background', this.className) } }src/app/components/blocks/feature-accordion-media/index.ts0.1 kB
export { UiFeatureAccordionMediaComponent, type AccordionMediaFeature, type AccordionMediaRow, } from './feature-accordion-media.component'
Raw manifest:https://uipkge.dev/r/angular/feature-accordion-media.json