Use Cases
blockmarketingUse-case set spanning a filterable industry grid, persona-switched tabs, self-select role cards, and a numbered end-to-end scenario walkthrough.
Installation
$pnpm dlx uipkge-ng@latest add use-cases$npx uipkge-ng@latest add use-cases$yarn dlx uipkge-ng@latest add use-cases$bunx uipkge-ng@latest add use-casesManifest:
https://uipkge.dev/r/angular/use-cases.jsonInstalls to:src/app/components/blocks/use-cases/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.
IndustryUseCaseinterface IndustryUseCase {
icon: unknown
segment: string
title: string
body: string
metric: string
}Personainterface Persona {
value: string
role: string
pain: string
outcomes: string[]
metric: { value: string; label: string }
cta: string
}npm dependencies
Files installed (5)
src/app/components/blocks/use-cases/use-cases-industry-grid.component.ts6.3 kB
// Filterable industry use-case grid: segment chips narrow a six-card grid in place, // each card carrying an icon, industry label, outcome headline, proof metric, and a // quiet read-more link. Port of the React block 1:1. import { ChangeDetectionStrategy, Component, Input, computed, signal } from '@angular/core' import { ArrowUpRight, Banknote, Factory, HeartPulse, LucideAngularModule, Ship, ShoppingCart, Truck, } from 'lucide-angular' import { cn } from '@/lib/utils' import { UiBadgeComponent } from '@/ui/badge/badge.component' import { UiButtonComponent } from '@/ui/button/button.component' import { UiCardComponent, UiCardContentComponent } from '@/ui/card/card.component' import { UiToggleGroupComponent, UiToggleGroupItemComponent } from '@/ui/toggle-group/toggle-group.component' interface IndustryUseCase { icon: unknown segment: string title: string body: string metric: string } const useCases: IndustryUseCase[] = [ { icon: Banknote, segment: 'Financial services', title: 'Close the books without a spreadsheet relay', body: 'Ledger-reconciled metrics that lock on period close, with an auditable trail for every restatement.', metric: '5 days off close', }, { icon: HeartPulse, segment: 'Healthcare', title: 'Report on care quality without exposing PHI', body: 'Row-level scoping evaluated per query, so a shared dashboard never leaks a chart the viewer cannot open.', metric: 'HIPAA-scoped by default', }, { icon: ShoppingCart, segment: 'Retail', title: 'One margin definition across every channel', body: 'Store, marketplace, and wholesale roll up to the same certified metric instead of three competing exports.', metric: '3 channels, 1 number', }, { icon: Truck, segment: 'Logistics', title: 'Track service levels against the contract', body: 'On-time thresholds encoded per customer contract, so breach reporting stops being a manual comparison.', metric: '98.2% SLA visibility', }, { icon: Factory, segment: 'Manufacturing', title: 'Yield reporting that survives a line change', body: 'Definitions version with the production line, so last quarter’s numbers stay comparable after a retool.', metric: '12 lines reconciled', }, { icon: Ship, segment: 'Logistics', title: 'Landed cost per shipment, not per guess', body: 'Freight, duty, and demurrage join at query time against the booking rather than a monthly allocation.', metric: 'Per-shipment costing', }, ] const ALL = 'All' @Component({ changeDetection: ChangeDetectionStrategy.Eager, selector: 'ui-use-cases-industry-grid, [ui-use-cases-industry-grid]', 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: [ LucideAngularModule, UiBadgeComponent, UiButtonComponent, UiCardComponent, UiCardContentComponent, UiToggleGroupComponent, UiToggleGroupItemComponent, ], template: ` <section data-slot="use-cases-industry-grid" [class]="rootClass"> <div class="mx-auto max-w-6xl px-6 py-20 lg:py-28"> <div class="flex flex-wrap items-end justify-between gap-6"> <div class="max-w-2xl"> <span ui-badge variant="secondary">By industry</span> <h2 class="mt-4 text-3xl font-semibold tracking-tight sm:text-4xl">Where teams start</h2> <p class="text-muted-foreground mt-3 text-lg"> Six rollouts we run often enough to have opinions about. Filter to yours. </p> </div> <div ui-toggle-group type="single" variant="outline" size="sm" class="flex-nowrap overflow-x-auto" aria-label="Filter use cases by industry" [value]="active()" (valueChange)="onSegmentChange($event)" > @for (segment of segments(); track segment) { <button ui-toggle-group-item [value]="segment">{{ segment }}</button> } </div> </div> <div class="mt-10 grid gap-4 sm:grid-cols-2 lg:grid-cols-3"> @for (useCase of visible(); track useCase.title) { <div ui-card class="group"> <div ui-card-content class="flex h-full flex-col p-6"> <lucide-icon [img]="useCase.icon" class="text-muted-foreground size-5" aria-hidden="true" /> <p class="text-muted-foreground mt-4 font-mono text-xs tracking-[0.14em] uppercase"> {{ useCase.segment }} </p> <h3 class="mt-2 text-base leading-snug font-semibold">{{ useCase.title }}</h3> <p class="text-muted-foreground mt-2 text-sm leading-relaxed">{{ useCase.body }}</p> <div class="border-border mt-auto flex items-center justify-between gap-3 border-t pt-4"> <span class="text-sm font-medium">{{ useCase.metric }}</span> <button ui-button variant="ghost" size="sm" class="h-auto px-2 py-1"> Read <lucide-icon [img]="ArrowUpRight" class="ml-1 size-3.5 transition-transform group-hover:translate-x-0.5 group-hover:-translate-y-0.5" aria-hidden="true" /> </button> </div> </div> </div> } </div> </div> </section> `, }) export class UiUseCasesIndustryGridComponent { protected readonly ArrowUpRight = ArrowUpRight @Input('class') className?: string @Input() set initialSegment(v: string | undefined) { if (v) this.active.set(v) } readonly active = signal(ALL) readonly segments = computed(() => [ALL, ...new Set(useCases.map((u) => u.segment))]) readonly visible = computed(() => this.active() === ALL ? useCases : useCases.filter((u) => u.segment === this.active()), ) get rootClass(): string { return cn('bg-background', this.className) } // ToggleGroup single-select emits '' when the active item is pressed again; // fall back to ALL so the grid can never end up empty. onSegmentChange(value: unknown): void { this.active.set(typeof value === 'string' && value ? value : ALL) } }src/app/components/blocks/use-cases/use-cases-persona-tabs.component.ts6.4 kB
// Persona-switched use cases: selecting a role swaps the pain-point summary, outcome // list, supporting metric, and CTA in place — no navigation, no layout shift between // tabs. Port of the React block 1:1. import { ChangeDetectionStrategy, Component, Input, signal } from '@angular/core' import { ArrowRight, Check, LucideAngularModule } from 'lucide-angular' import { cn } from '@/lib/utils' import { UiBadgeComponent } from '@/ui/badge/badge.component' import { UiButtonComponent } from '@/ui/button/button.component' import { UiCardComponent, UiCardContentComponent } from '@/ui/card/card.component' import { UiTabsComponent, UiTabsContentComponent, UiTabsListComponent, UiTabsTriggerComponent, } from '@/ui/tabs/tabs.component' interface Persona { value: string role: string pain: string outcomes: string[] metric: { value: string; label: string } cta: string } const personas: Persona[] = [ { value: 'analytics', role: 'Data & analytics', pain: 'Every request becomes a bespoke SQL ticket, and nobody trusts the number in the deck.', outcomes: [ 'Publish one certified metric definition per number', 'Retire the ad-hoc query queue without losing coverage', 'Trace any figure back to the column it came from', ], metric: { value: '71%', label: 'fewer ad-hoc data requests after one quarter' }, cta: 'See the analyst workflow', }, { value: 'finance', role: 'Finance', pain: 'Close depends on four spreadsheets and one person who knows how they link.', outcomes: [ 'Reconcile against the ledger before anything publishes', 'Lock a period so restated figures cannot drift', 'Hand auditors a change log instead of a folder', ], metric: { value: '5 days', label: 'cut from the average monthly close' }, cta: 'See the close workflow', }, { value: 'revops', role: 'RevOps', pain: 'Pipeline reporting disagrees with the CRM, so forecast reviews start with an argument.', outcomes: [ 'One pipeline definition shared by sales and finance', 'Stage changes tracked with the rep and the timestamp', 'Forecast snapshots you can diff week over week', ], metric: { value: '2.4pp', label: 'tighter forecast accuracy band' }, cta: 'See the forecast workflow', }, { value: 'engineering', role: 'Engineering', pain: 'The reporting layer is an undocumented service nobody wants to own.', outcomes: [ 'Definitions live in the repo, reviewed like any other change', 'No bespoke ETL to babysit at 3am', 'Warehouse cost ceilings enforced per team', ], metric: { value: '0', label: 'bespoke pipelines to maintain' }, cta: 'See the engineering setup', }, ] @Component({ changeDetection: ChangeDetectionStrategy.Eager, selector: 'ui-use-cases-persona-tabs, [ui-use-cases-persona-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: [ LucideAngularModule, UiBadgeComponent, UiButtonComponent, UiCardComponent, UiCardContentComponent, UiTabsComponent, UiTabsContentComponent, UiTabsListComponent, UiTabsTriggerComponent, ], template: ` <section data-slot="use-cases-persona-tabs" [class]="rootClass"> <div class="mx-auto max-w-5xl px-6 py-20 lg:py-28"> <div class="max-w-2xl"> <span ui-badge variant="secondary">Use cases</span> <h2 class="mt-4 text-3xl font-semibold tracking-tight sm:text-4xl">Pick the seat you sit in</h2> <p class="text-muted-foreground mt-3 text-lg"> Same platform, four very different first weeks. Start with the one that matches your job. </p> </div> <ui-tabs [value]="activeTab()" (valueChange)="activeTab.set($event)" class="mt-10"> <!-- flex-nowrap + horizontal scroll: the trigger row keeps its height whether there are three roles or thirty. --> <ui-tabs-list variant="segmented" class="flex-nowrap overflow-x-auto"> @for (persona of personas; track persona.value) { <button ui-tabs-trigger [value]="persona.value" variant="segmented"> {{ persona.role }} </button> } </ui-tabs-list> @for (persona of personas; track persona.value) { <ui-tabs-content [value]="persona.value" class="mt-8"> <!-- min-h keeps the panel from collapsing between tabs with shorter copy. --> <div class="grid min-h-[19rem] gap-8 lg:grid-cols-[1.3fr_1fr]"> <div> <p class="text-muted-foreground text-sm">Today</p> <p class="mt-1.5 text-lg leading-snug font-medium text-balance">{{ persona.pain }}</p> <p class="text-muted-foreground mt-7 text-sm">With uipkge</p> <ul class="mt-3 space-y-3"> @for (outcome of persona.outcomes; track outcome) { <li class="flex items-start gap-2.5 text-sm"> <lucide-icon [img]="Check" class="text-primary mt-0.5 size-4 shrink-0" aria-hidden="true" /> <span>{{ outcome }}</span> </li> } </ul> <button ui-button class="mt-7"> {{ persona.cta }} <lucide-icon [img]="ArrowRight" class="ml-2 size-4" aria-hidden="true" /> </button> </div> <div ui-card class="self-start"> <div ui-card-content class="p-6"> <p class="font-display text-4xl font-bold tracking-tight">{{ persona.metric.value }}</p> <p class="text-muted-foreground mt-2 text-sm leading-relaxed">{{ persona.metric.label }}</p> </div> </div> </div> </ui-tabs-content> } </ui-tabs> </div> </section> `, }) export class UiUseCasesPersonaTabsComponent { protected readonly ArrowRight = ArrowRight protected readonly Check = Check protected readonly personas = personas @Input('class') className?: string @Input() set initialPersona(v: string | undefined) { if (v) this.activeTab.set(v) } readonly activeTab = signal('analytics') get rootClass(): string { return cn('bg-background', this.className) } }src/app/components/blocks/use-cases/use-cases-role-cards.component.ts4.8 kB
// Role cards stating the job to be done, the first week of work, and the metric that // role is measured on — self-selection without a tab interaction. Port of the React // block 1:1. import { ChangeDetectionStrategy, Component, Input } from '@angular/core' import { ArrowUpRight, LucideAngularModule } from 'lucide-angular' import { cn } from '@/lib/utils' import { UiAvatarComponent, UiAvatarFallbackComponent } from '@/ui/avatar/avatar.component' import { UiBadgeComponent } from '@/ui/badge/badge.component' import { UiButtonComponent } from '@/ui/button/button.component' import { UiCardComponent, UiCardContentComponent } from '@/ui/card/card.component' import { UiSeparatorComponent } from '@/ui/separator/separator.component' // Naming the metric each role is judged on is what lets someone self-select // without reading all four cards. const roles = [ { initials: 'FC', role: 'Financial controller', job: 'Close the books without a reconciliation relay.', week: 'Reconcile revenue and margin against the ledger, then lock the period.', metric: 'Days to close', }, { initials: 'AL', role: 'Analytics lead', job: 'Stop being a query queue for the rest of the company.', week: 'Publish the ten numbers that generate the most tickets as certified metrics.', metric: 'Ad-hoc requests per month', }, { initials: 'RO', role: 'RevOps', job: 'Make forecast review about the forecast.', week: 'Agree one pipeline definition with finance and snapshot it weekly.', metric: 'Forecast accuracy band', }, { initials: 'DE', role: 'Data engineer', job: 'Stop owning an undocumented reporting service.', week: 'Move definitions into the repo and delete the bespoke extract jobs.', metric: 'Pipelines maintained', }, ] @Component({ changeDetection: ChangeDetectionStrategy.Eager, selector: 'ui-use-cases-role-cards, [ui-use-cases-role-cards]', 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: [ LucideAngularModule, UiAvatarComponent, UiAvatarFallbackComponent, UiBadgeComponent, UiButtonComponent, UiCardComponent, UiCardContentComponent, UiSeparatorComponent, ], template: ` <section data-slot="use-cases-role-cards" [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">By role</span> <h2 class="mt-4 text-3xl font-semibold tracking-tight sm:text-4xl">Find the card with your job on it</h2> <p class="text-muted-foreground mt-3 text-lg"> Each one names the work of the first week and the number that role is actually judged on. </p> </div> <div class="mt-10 grid gap-4 sm:grid-cols-2"> @for (role of roles; track role.role) { <div ui-card class="group"> <div ui-card-content class="flex h-full flex-col p-6"> <div class="flex items-center gap-3"> <ui-avatar class="size-9"> <ui-avatar-fallback class="text-xs">{{ role.initials }}</ui-avatar-fallback> </ui-avatar> <p class="text-sm font-semibold">{{ role.role }}</p> </div> <p class="mt-4 text-base leading-snug font-medium text-balance">{{ role.job }}</p> <div ui-separator class="my-4"></div> <p class="text-muted-foreground font-mono text-xs tracking-[0.14em] uppercase">First week</p> <p class="text-muted-foreground mt-2 text-sm leading-relaxed">{{ role.week }}</p> <div class="border-border mt-auto flex items-center justify-between gap-3 border-t pt-4"> <span class="text-xs"> <span class="text-muted-foreground">Measured on </span> <span class="font-medium">{{ role.metric }}</span> </span> <button ui-button variant="ghost" size="sm" class="h-auto shrink-0 px-2 py-1"> Read <lucide-icon [img]="ArrowUpRight" class="ml-1 size-3.5 transition-transform group-hover:translate-x-0.5 group-hover:-translate-y-0.5" aria-hidden="true" /> </button> </div> </div> </div> } </div> </div> </section> `, }) export class UiUseCasesRoleCardsComponent { protected readonly ArrowUpRight = ArrowUpRight protected readonly roles = roles @Input('class') className?: string get rootClass(): string { return cn('bg-background', this.className) } }src/app/components/blocks/use-cases/use-cases-scenario-walkthrough.component.ts4.7 kB
// A single scenario walked end to end: what happens, who does it, and what the // system records at each step. Port of the React block 1:1. import { ChangeDetectionStrategy, Component, Input } from '@angular/core' import { ArrowRight, LucideAngularModule } from 'lucide-angular' import { cn } from '@/lib/utils' import { UiBadgeComponent } from '@/ui/badge/badge.component' import { UiButtonComponent } from '@/ui/button/button.component' import { UiCardComponent, UiCardContentComponent } from '@/ui/card/card.component' import { UiSeparatorComponent } from '@/ui/separator/separator.component' // One concrete scenario beats four abstract ones: the reader can check it // against their own Tuesday. const steps = [ { actor: 'Controller', action: 'Notices Q1 revenue in the board deck is £41k above the close pack.', recorded: 'Nothing yet — this is the part that used to take a day to explain.', }, { actor: 'Controller', action: 'Opens the metric and reads its history instead of asking the analytics team.', recorded: 'Definition v127, changed 14 Feb by finance-analytics, reviewed by A. Reyes.', }, { actor: 'Analyst', action: 'Sees the deck was built on v126, before the trailing window changed.', recorded: 'Consumer list shows which dashboards still resolve the older version.', }, { actor: 'Analyst', action: 'Republishes the deck against the current definition; the gap disappears.', recorded: 'Publish event, with the before and after figures attached.', }, { actor: 'Auditor', action: 'Six months later, asks why Q1 was restated.', recorded: 'The whole chain above, exported as a single PDF in under a minute.', }, ] @Component({ changeDetection: ChangeDetectionStrategy.Eager, selector: 'ui-use-cases-scenario-walkthrough, [ui-use-cases-scenario-walkthrough]', 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: [ LucideAngularModule, UiBadgeComponent, UiButtonComponent, UiCardComponent, UiCardContentComponent, UiSeparatorComponent, ], template: ` <section data-slot="use-cases-scenario-walkthrough" [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">A Tuesday</span> <h2 class="mt-4 text-3xl font-semibold tracking-tight text-balance sm:text-4xl"> Two numbers disagree. Here is the whole sequence. </h2> <p class="text-muted-foreground mt-3 text-lg"> Not a feature list — the actual path, including what the system writes down at each step. </p> </div> <ol class="mt-12 space-y-4"> @for (step of steps; track step.action; let i = $index) { <li class="grid grid-cols-[auto_1fr] gap-x-5"> <div class="flex flex-col items-center"> <span class="border-border bg-card text-muted-foreground relative z-10 flex size-9 shrink-0 items-center justify-center rounded-full border font-mono text-xs font-semibold" > {{ stepNumber(i) }} </span> @if (i < steps.length - 1) { <span class="bg-border w-px grow" aria-hidden="true"></span> } </div> <div ui-card class="mb-4"> <div ui-card-content class="p-5"> <span ui-badge variant="outline">{{ step.actor }}</span> <p class="mt-3 text-sm leading-relaxed">{{ step.action }}</p> <div ui-separator class="my-3"></div> <p class="text-muted-foreground text-xs leading-relaxed"> <span class="font-medium">Recorded: </span>{{ step.recorded }} </p> </div> </div> </li> } </ol> <div class="border-border mt-6 flex flex-wrap items-center justify-between gap-4 border-t pt-6"> <p class="text-muted-foreground text-sm">Total elapsed, start to resolution: about nine minutes.</p> <button ui-button> Walk through it yourself <lucide-icon [img]="ArrowRight" class="ml-2 size-4" aria-hidden="true" /> </button> </div> </div> </section> `, }) export class UiUseCasesScenarioWalkthroughComponent { protected readonly ArrowRight = ArrowRight protected readonly steps = steps @Input('class') className?: string get rootClass(): string { return cn('bg-background', this.className) } stepNumber(index: number): string { return String(index + 1).padStart(2, '0') } }src/app/components/blocks/use-cases/index.ts0.3 kB
export { UiUseCasesIndustryGridComponent } from './use-cases-industry-grid.component' export { UiUseCasesPersonaTabsComponent } from './use-cases-persona-tabs.component' export { UiUseCasesRoleCardsComponent } from './use-cases-role-cards.component' export { UiUseCasesScenarioWalkthroughComponent } from './use-cases-scenario-walkthrough.component'
Raw manifest:https://uipkge.dev/r/angular/use-cases.json