Menu

Change language

GitHub repo

Before & After — Toggle

blockmarketing

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.

Installation

$npx uipkge-ng@latest add before-after-toggle
Manifest:https://uipkge.dev/r/angular/before-after-toggle.jsonInstalls to:src/app/components/blocks/before-after-toggle/

Variants

Loading interactive previews…

Schema

Type aliases exported from this item's source. Use these to shape the data you pass in.

BeforeAfterRow
interface BeforeAfterRow {
  source: string
  value: string
  tone: 'on' | 'off'
}
BeforeAfterState
interface BeforeAfterState {
  id: string
  label: string
  caption: string
  changed: string
  rows: BeforeAfterRow[]
}

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

Raw manifest:https://uipkge.dev/r/angular/before-after-toggle.json