FAQ — Categorized Tabs
blockmarketingQuestion set grouped behind category tabs, each panel holding its own accordion with a count badge, for support pages where one flat list would run past thirty entries.
Installation
$pnpm dlx uipkge-ng@latest add faq-categorized-tabs$npx uipkge-ng@latest add faq-categorized-tabs$yarn dlx uipkge-ng@latest add faq-categorized-tabs$bunx uipkge-ng@latest add faq-categorized-tabsManifest:
https://uipkge.dev/r/angular/faq-categorized-tabs.jsonInstalls to:src/app/components/blocks/faq-categorized-tabs/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.
FaqEntryinterface FaqEntry {
q: string
a: string
}FaqCategoryinterface FaqCategory {
id: string
label: string
entries: FaqEntry[]
}npm dependencies
Files installed (2)
src/app/components/blocks/faq-categorized-tabs/faq-categorized-tabs.component.ts5 kB
// Question set grouped behind category tabs, each panel holding its own accordion with a // count badge. Port of the React block 1:1. import { ChangeDetectionStrategy, Component, Input, 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 { UiTabsComponent, UiTabsContentComponent, UiTabsListComponent, UiTabsTriggerComponent, } from '@/ui/tabs/tabs.component' interface FaqEntry { q: string a: string } interface FaqCategory { id: string label: string entries: FaqEntry[] } const categories: FaqCategory[] = [ { id: 'security', label: 'Security', entries: [ { q: 'Where is our data stored?', a: 'Definitions and query metadata sit in your chosen region. Warehouse rows are never copied out.', }, { q: 'Are you SOC 2 audited?', a: 'Type II, audited annually. The report and the subprocessor list are on the trust centre.', }, { q: 'Can we bring our own keys?', a: 'Yes, on the enterprise plan, with rotation handled through your KMS.' }, ], }, { id: 'billing', label: 'Billing', entries: [ { q: 'How is usage counted?', a: 'By query, not by seat. Materialised reads are not billed twice within their freshness window.', }, { q: 'Can we cap spend?', a: 'Per-team query budgets are enforced at run time, so a runaway dashboard cannot outspend its ceiling.', }, { q: 'Do you offer annual terms?', a: 'Yes, with a discount and an invoiced option above a threshold.' }, ], }, { id: 'setup', label: 'Setup', entries: [ { q: 'What do you need from us?', a: 'A read-only role, your identity provider, and an hour with whoever owns the warehouse.', }, { q: 'How long until first value?', a: 'Most teams publish a first certified metric on day one and finish the rollout in five weeks.', }, ], }, ] @Component({ changeDetection: ChangeDetectionStrategy.Eager, selector: 'ui-faq-categorized-tabs, [ui-faq-categorized-tabs]', 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, UiTabsComponent, UiTabsContentComponent, UiTabsListComponent, UiTabsTriggerComponent, ], template: ` <section data-slot="faq-categorized-tabs" [class]="rootClass"> <div class="mx-auto max-w-4xl px-6 py-20 lg:py-28"> <div class="max-w-2xl"> <span ui-badge variant="secondary">Support</span> <h2 class="mt-4 text-3xl font-semibold tracking-tight sm:text-4xl">Answers by topic</h2> <p class="text-muted-foreground mt-3 text-lg"> Grouped rather than listed, because one flat list of thirty questions is a list nobody reads. </p> </div> <ui-tabs [value]="activeTab()" (valueChange)="activeTab.set($event)" class="mt-10"> <ui-tabs-list variant="pill" class="flex-nowrap overflow-x-auto"> @for (category of categories; track category.id) { <button ui-tabs-trigger [value]="category.id" variant="pill"> {{ category.label }} <span class="text-muted-foreground ml-1.5 font-mono text-xs">{{ category.entries.length }}</span> </button> } </ui-tabs-list> @for (category of categories; track category.id) { <ui-tabs-content [value]="category.id" class="mt-6"> <ui-accordion type="single" collapsible class="w-full"> @for (entry of category.entries; track entry.q) { <ui-accordion-item [value]="entry.q"> <button ui-accordion-trigger class="text-left font-medium">{{ entry.q }}</button> <ui-accordion-content class="text-muted-foreground leading-relaxed">{{ entry.a }}</ui-accordion-content> </ui-accordion-item> } </ui-accordion> </ui-tabs-content> } </ui-tabs> <button ui-button variant="ghost" class="mt-8">Browse the full help centre</button> </div> </section> `, }) export class UiFaqCategorizedTabsComponent { protected readonly categories = categories @Input('class') className?: string @Input() set defaultTab(v: string) { if (v) this.activeTab.set(v) } readonly activeTab = signal(categories[0].id) get rootClass(): string { return cn('bg-background', this.className) } }src/app/components/blocks/faq-categorized-tabs/index.ts0.1 kB
export { UiFaqCategorizedTabsComponent } from './faq-categorized-tabs.component'
Raw manifest:https://uipkge.dev/r/angular/faq-categorized-tabs.json