Menu

Change language

GitHub repo

Before & After — Side by Side

blockmarketing

Two labelled panels set side by side at equal height with a shared annotation row beneath, for comparisons a slider would make harder rather than clearer.

Installation

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

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 {
  text: string
  note: string
}
BeforeAfterPanel
interface BeforeAfterPanel {
  label: string
  caption: string
  rows: BeforeAfterRow[]
}
BeforeAfterAnnotation
interface BeforeAfterAnnotation {
  id: string
  text: string
}

Files installed (2)

  • src/app/components/blocks/before-after-side-by-side/before-after-side-by-side.component.ts5.1 kB
    // Two labelled panels set side by side at equal height with a shared annotation
    // row beneath, for comparisons a slider would make harder rather than clearer.
    // Port of the Vue/React block 1:1 — same Before/After file rows, same numbered
    // annotations referenced from both panels, same case-study CTA.
    import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
    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 { cn } from '@/lib/utils'
    
    interface BeforeAfterRow {
      text: string
      note: string
    }
    
    interface BeforeAfterPanel {
      label: string
      caption: string
      rows: BeforeAfterRow[]
    }
    
    interface BeforeAfterAnnotation {
      id: string
      text: string
    }
    
    // Annotations are numbered and referenced from both panels, which is what makes
    // a side-by-side legible where a drag slider hides half the evidence.
    const panels: BeforeAfterPanel[] = [
      {
        label: 'Before',
        caption: 'Four exports reconciled by hand',
        rows: [
          { text: 'revenue_q1_final_v3.xlsx', note: '1' },
          { text: 'revenue_q1_FINAL_v3b.xlsx', note: '1' },
          { text: 'refunds_manual_adj.csv', note: '2' },
          { text: 'fx_rates_pasted.csv', note: '3' },
        ],
      },
      {
        label: 'After',
        caption: 'One certified definition',
        rows: [
          { text: 'metrics/revenue_net.yml', note: '1' },
          { text: '  joins: refunds, fx_rates', note: '2' },
          { text: '  owner: finance-analytics', note: '3' },
          { text: '  reconciled: 4 quarters', note: '' },
        ],
      },
    ]
    
    const annotations: BeforeAfterAnnotation[] = [
      { id: '1', text: 'Two files claiming to be final, differing by 40k in net revenue.' },
      { id: '2', text: 'Refund adjustments applied by hand, with no record of who applied them.' },
      { id: '3', text: 'FX rates pasted from a second system on a date nobody logged.' },
    ]
    
    @Component({
      changeDetection: ChangeDetectionStrategy.Eager,
      selector: 'ui-before-after-side-by-side, [ui-before-after-side-by-side]',
      standalone: true,
      // The block renders its own root <section>: keep this wrapper out of the layout.
      host: { '[attr.class]': '"contents"' },
      imports: [UiBadgeComponent, UiButtonComponent, UiCardComponent, UiCardContentComponent, UiSeparatorComponent],
      template: `
        <section data-slot="before-after-side-by-side" [class]="rootClass">
          <div class="mx-auto max-w-5xl px-6 py-20 lg:py-28">
            <span ui-badge variant="secondary">Before and after</span>
            <h2 class="mt-4 text-3xl font-semibold tracking-tight sm:text-4xl">The same close, two quarters apart</h2>
    
            <!-- items-stretch so both panels share a height; a slider would hide one
                 side at a time and make the annotations unreadable. -->
            <div class="mt-10 grid items-stretch gap-4 md:grid-cols-2">
              @for (panel of panels; track panel.label) {
                <div ui-card>
                  <div ui-card-content class="flex h-full flex-col 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">{{ panel.label }}</span>
                      <span class="text-muted-foreground text-xs">{{ panel.caption }}</span>
                    </div>
                    <ul class="flex-1 space-y-2 p-4">
                      @for (row of panel.rows; track row.text) {
                        <li class="flex items-start gap-2">
                          @if (row.note) {
                            <span
                              class="border-border text-muted-foreground mt-px flex size-4 shrink-0 items-center justify-center rounded-full border font-mono text-xs"
                            >
                              {{ row.note }}
                            </span>
                          } @else {
                            <span class="size-4 shrink-0" aria-hidden="true"></span>
                          }
                          <span class="min-w-0 font-mono text-xs break-all">{{ row.text }}</span>
                        </li>
                      }
                    </ul>
                  </div>
                </div>
              }
            </div>
    
            <ui-separator class="my-8" />
    
            <ol class="space-y-3">
              @for (annotation of annotations; track annotation.id) {
                <li class="flex items-start gap-3 text-sm">
                  <span
                    class="border-border text-muted-foreground mt-0.5 flex size-5 shrink-0 items-center justify-center rounded-full border font-mono text-xs"
                  >
                    {{ annotation.id }}
                  </span>
                  <span class="text-muted-foreground leading-relaxed">{{ annotation.text }}</span>
                </li>
              }
            </ol>
    
            <button ui-button class="mt-8">Read the full case study</button>
          </div>
        </section>
      `,
    })
    export class UiBeforeAfterSideBySideComponent {
      protected readonly panels = panels
      protected readonly annotations = annotations
    
      @Input('class') className?: string
    
      get rootClass(): string {
        return cn('bg-background', this.className)
      }
    }
    
  • src/app/components/blocks/before-after-side-by-side/index.ts0.1 kB

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