Before & After — Toggle
blockmarketingSingle panel toggling between the old and new state of the same screen, with a caption naming what changed and a control that keeps its position between switches.
Installation
$pnpm dlx uipkge-ng@latest add before-after-toggle$npx uipkge-ng@latest add before-after-toggle$yarn dlx uipkge-ng@latest add before-after-toggle$bunx uipkge-ng@latest add before-after-toggleManifest:
https://uipkge.dev/r/angular/before-after-toggle.jsonInstalls to:src/app/components/blocks/before-after-toggle/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.
BeforeAfterRowinterface BeforeAfterRow {
source: string
value: string
tone: 'on' | 'off'
}BeforeAfterStateinterface BeforeAfterState {
id: string
label: string
caption: string
changed: string
rows: BeforeAfterRow[]
}npm dependencies
Files installed (2)
src/app/components/blocks/before-after-toggle/before-after-toggle.component.ts5.1 kB
// Single panel toggling between the old and new state of the same screen, with a // caption naming what changed and a control that keeps its position between switches. // Port of the React block 1:1. import { ChangeDetectionStrategy, Component, Input, computed, signal } from '@angular/core' 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' import { UiToggleGroupComponent, UiToggleGroupItemComponent } from '@/ui/toggle-group/toggle-group.component' export interface BeforeAfterRow { source: string value: string tone: 'on' | 'off' } export interface BeforeAfterState { id: string label: string caption: string changed: string rows: BeforeAfterRow[] } const states: BeforeAfterState[] = [ { id: 'before', label: 'Before', caption: 'Three dashboards, three revenue numbers', changed: 'Each team maintained its own formula, and none of them agreed at close.', rows: [ { source: 'Exec summary', value: '£4,182,400', tone: 'off' }, { source: 'Finance close pack', value: '£4,140,900', tone: 'off' }, { source: 'Board deck', value: '£4,206,150', tone: 'off' }, ], }, { id: 'after', label: 'After', caption: 'One certified definition', changed: 'All three resolve the same definition, so the reconciliation step disappeared.', rows: [ { source: 'Exec summary', value: '£4,140,900', tone: 'on' }, { source: 'Finance close pack', value: '£4,140,900', tone: 'on' }, { source: 'Board deck', value: '£4,140,900', tone: 'on' }, ], }, ] @Component({ changeDetection: ChangeDetectionStrategy.Eager, selector: 'ui-before-after-toggle, [ui-before-after-toggle]', 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: [ UiBadgeComponent, UiButtonComponent, UiCardComponent, UiCardContentComponent, UiSeparatorComponent, UiToggleGroupComponent, UiToggleGroupItemComponent, ], template: ` <section data-slot="before-after-toggle" [class]="rootClass"> <div class="mx-auto max-w-3xl px-6 py-20 lg:py-28"> <div class="flex flex-wrap items-end justify-between gap-6"> <div> <span ui-badge variant="secondary">Before and after</span> <h2 class="mt-4 text-3xl font-semibold tracking-tight sm:text-4xl">One number, three places</h2> </div> <!-- The control keeps its position across switches: moving it would make the comparison harder than just showing both. --> <div ui-toggle-group type="single" variant="outline" size="sm" aria-label="Show the state before or after" [value]="active()" (valueChange)="onChange($event)" > @for (state of toggleStates; track state.id) { <button ui-toggle-group-item [value]="state.id">{{ state.label }}</button> } </div> </div> <div ui-card class="mt-8"> <div ui-card-content class="p-0"> <div class="border-border flex items-center justify-between gap-3 border-b px-4 py-2.5"> <span class="text-sm font-semibold">{{ current().label }}</span> <span class="text-muted-foreground text-xs">{{ current().caption }}</span> </div> <!-- Fixed height across both states so toggling never resizes the card. --> <ul class="divide-border min-h-[11rem] divide-y"> @for (row of current().rows; track row.source) { <li class="flex items-center justify-between gap-4 px-4 py-4"> <span class="text-sm">{{ row.source }}</span> <span [class]="rowClass(row.tone)">{{ row.value }}</span> </li> } </ul> <ui-separator /> <p class="text-muted-foreground px-4 py-3 text-xs leading-relaxed">{{ current().changed }}</p> </div> </div> <button ui-button variant="ghost" class="mt-6">How certification works</button> </div> </section> `, }) export class UiBeforeAfterToggleComponent { @Input() set initialActive(v: string) { if (v === 'before' || v === 'after') this.active.set(v) } @Input('class') className?: string protected readonly toggleStates = states readonly active = signal('before') readonly current = computed(() => states.find((state) => state.id === this.active()) ?? states[0]!) get rootClass(): string { return cn('bg-background', this.className) } rowClass(tone: 'on' | 'off'): string { return cn('font-mono text-sm tabular-nums', tone === 'on' ? 'text-success font-semibold' : 'text-muted-foreground') } onChange(value: unknown): void { if (typeof value === 'string' && value) this.active.set(value) } }src/app/components/blocks/before-after-toggle/index.ts0.1 kB
export { UiBeforeAfterToggleComponent, type BeforeAfterRow, type BeforeAfterState, } from './before-after-toggle.component'
Raw manifest:https://uipkge.dev/r/angular/before-after-toggle.json