Menu

Change language

GitHub repo

Features — Segmented Compare

blockmarketing

Segmented control switching one feature section between two operating modes, with the capability list, constraints, and a recommendation line changing per mode.

Installation

$npx uipkge-ng@latest add feature-segmented-compare
Manifest:https://uipkge.dev/r/angular/feature-segmented-compare.jsonInstalls to:src/app/components/blocks/feature-segmented-compare/

Variants

Loading interactive previews…

Schema

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

ExecutionMode
interface ExecutionMode {
  id: string
  label: string
  summary: string
  capabilities: string[]
  constraints: string[]
  recommendation: string
}

Files installed (2)

  • src/app/components/blocks/feature-segmented-compare/feature-segmented-compare.component.ts6.2 kB
    // Segmented execution-mode comparison: one toggle switches the whole section between
    // two operating modes, swapping the summary, capability list, constraints, and the
    // recommendation line. Port of the React block 1:1.
    import { ChangeDetectionStrategy, Component, Input, computed, signal } from '@angular/core'
    import { Check, LucideAngularModule, TriangleAlert } 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'
    import { UiToggleGroupComponent, UiToggleGroupItemComponent } from '@/ui/toggle-group/toggle-group.component'
    
    export interface ExecutionMode {
      id: string
      label: string
      summary: string
      capabilities: string[]
      constraints: string[]
      recommendation: string
    }
    
    // Each mode states its constraints as plainly as its capabilities. A comparison
    // that only lists upsides makes the reader do the work of distrusting it.
    const modes: ExecutionMode[] = [
      {
        id: 'live',
        label: 'Live query',
        summary: 'Every read hits the warehouse at request time.',
        capabilities: [
          'Always current, to the second',
          'No storage footprint on our side',
          'Row-level scope applied per query',
        ],
        constraints: ['Latency follows your warehouse', 'Each read is billable compute'],
        recommendation: 'Use for exploratory analysis and anything finance signs off on.',
      },
      {
        id: 'materialised',
        label: 'Materialised',
        summary: 'Aggregates are computed on a schedule you set.',
        capabilities: [
          'Sub-100ms reads at any concurrency',
          'Warehouse cost bounded by the schedule',
          'Freshness stamp shown with every figure',
        ],
        constraints: ['Stale between runs, by design', 'Backfills reprocess the window'],
        recommendation: 'Use for dashboards opened dozens of times an hour.',
      },
    ]
    
    @Component({
      changeDetection: ChangeDetectionStrategy.Eager,
      selector: 'ui-feature-segmented-compare, [ui-feature-segmented-compare]',
      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,
        UiToggleGroupComponent,
        UiToggleGroupItemComponent,
      ],
      template: `
        <section data-slot="feature-segmented-compare" [class]="rootClass">
          <div class="mx-auto max-w-4xl px-6 py-20 lg:py-28">
            <div class="flex flex-wrap items-end justify-between gap-6">
              <div class="max-w-xl">
                <span ui-badge variant="secondary">Execution modes</span>
                <h2 class="mt-4 text-3xl font-semibold tracking-tight sm:text-4xl">Pick per metric, not per platform</h2>
              </div>
    
              <div
                ui-toggle-group
                type="single"
                variant="outline"
                size="sm"
                aria-label="Choose an execution mode"
                [value]="active()"
                (valueChange)="onChange($event)"
              >
                @for (mode of allModes; track mode.id) {
                  <button ui-toggle-group-item [value]="mode.id">{{ mode.label }}</button>
                }
              </div>
            </div>
    
            <!-- min-h keeps the card from resizing between modes with different list
                 lengths, which would otherwise shift the page on every toggle. -->
            <div ui-card class="mt-8">
              <div ui-card-content class="min-h-[22rem] p-6">
                <p class="text-lg leading-snug font-medium text-balance">{{ current().summary }}</p>
    
                <ui-separator class="my-6" />
    
                <div class="grid gap-8 sm:grid-cols-2">
                  <div>
                    <p class="text-muted-foreground font-mono text-xs tracking-[0.14em] uppercase">What you get</p>
                    <ul class="mt-3 space-y-2.5">
                      @for (capability of current().capabilities; track capability) {
                        <li class="flex items-start gap-2.5 text-sm">
                          <lucide-icon [img]="Check" class="text-success mt-0.5 size-4 shrink-0" aria-hidden="true" />
                          <span>{{ capability }}</span>
                        </li>
                      }
                    </ul>
                  </div>
    
                  <div>
                    <p class="text-muted-foreground font-mono text-xs tracking-[0.14em] uppercase">What it costs you</p>
                    <ul class="mt-3 space-y-2.5">
                      @for (constraint of current().constraints; track constraint) {
                        <li class="flex items-start gap-2.5 text-sm">
                          <lucide-icon
                            [img]="TriangleAlert"
                            class="text-muted-foreground mt-0.5 size-4 shrink-0"
                            aria-hidden="true"
                          />
                          <span class="text-muted-foreground">{{ constraint }}</span>
                        </li>
                      }
                    </ul>
                  </div>
                </div>
    
                <ui-separator class="my-6" />
    
                <p class="text-sm">
                  <span class="font-medium">Recommendation. </span>
                  <span class="text-muted-foreground">{{ current().recommendation }}</span>
                </p>
              </div>
            </div>
    
            <button ui-button variant="ghost" class="mt-6">Read how the planner chooses</button>
          </div>
        </section>
      `,
    })
    export class UiFeatureSegmentedCompareComponent {
      protected readonly Check = Check
      protected readonly TriangleAlert = TriangleAlert
    
      protected readonly allModes = modes
    
      @Input() set initialMode(v: string) {
        if (modes.some((m) => m.id === v)) this.active.set(v)
      }
      @Input('class') className?: string
    
      readonly active = signal(modes[0]!.id)
    
      readonly current = computed(() => modes.find((mode) => mode.id === this.active()) ?? modes[0]!)
    
      get rootClass(): string {
        return cn('bg-background', this.className)
      }
    
      onChange(value: unknown): void {
        if (typeof value === 'string' && value) this.active.set(value)
      }
    }
    
  • src/app/components/blocks/feature-segmented-compare/index.ts0.1 kB

Raw manifest:https://uipkge.dev/r/angular/feature-segmented-compare.json