Menu

Change language

GitHub repo

How It Works — Three Steps

blockmarketing

Horizontal three-step explainer with numbered markers on a connector rail, per-step icon, body copy, and a time estimate, closing on a primary + ghost CTA row.

Installation

$npx uipkge-ng@latest add how-it-works-steps
Manifest:https://uipkge.dev/r/angular/how-it-works-steps.jsonInstalls to:src/app/components/blocks/how-it-works-steps/

Variants

Loading interactive previews…

Schema

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

HowItWorksStep
interface HowItWorksStep {
  icon: LucideIconData
  title: string
  body: string
  detail: string
}

npm dependencies

Includes

Files installed (2)

  • src/app/components/blocks/how-it-works-steps/how-it-works-steps.component.ts4.2 kB
    // Horizontal three-step explainer with numbered markers on a connector rail, per-step
    // icon, body copy, and a time estimate, closing on a primary + ghost CTA row.
    // Port of the React block 1:1.
    import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
    import { CloudUpload, LucideAngularModule, Plug, Rocket, type LucideIconData } from 'lucide-angular'
    import { cn } from '@/lib/utils'
    import { UiBadgeComponent } from '@/ui/badge/badge.component'
    import { UiButtonComponent } from '@/ui/button/button.component'
    
    export interface HowItWorksStep {
      icon: LucideIconData
      title: string
      body: string
      detail: string
    }
    
    const steps: HowItWorksStep[] = [
      {
        icon: Plug,
        title: 'Connect your stack',
        body: 'Point us at Postgres, Snowflake, or a CSV drop. Read-only credentials, no agent to install.',
        detail: '~4 min',
      },
      {
        icon: CloudUpload,
        title: 'Map your metrics',
        body: 'Pick the columns that matter. We infer types, generate the schema, and flag anything ambiguous.',
        detail: '~10 min',
      },
      {
        icon: Rocket,
        title: 'Ship the dashboard',
        body: 'Publish to your team with row-level permissions already applied. Iterate without a redeploy.',
        detail: 'Same day',
      },
    ]
    
    @Component({
      changeDetection: ChangeDetectionStrategy.Eager,
      selector: 'ui-how-it-works-steps, [ui-how-it-works-steps]',
      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],
      template: `
        <section data-slot="how-it-works-steps" [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">How it works</span>
              <h2 class="mt-4 text-3xl font-semibold tracking-tight sm:text-4xl">
                Three steps from credentials to dashboard
              </h2>
              <p class="text-muted-foreground mt-3 text-lg">
                No migration, no rewrite. Most teams finish the whole path in an afternoon.
              </p>
            </div>
    
            <div class="relative mt-14">
              <!-- Connector rail: decorative, so it lives outside the <ol> rather than
                   adding a phantom list item for screen readers. -->
              <div
                class="bg-border pointer-events-none absolute top-6 right-[16.67%] left-[16.67%] hidden h-px md:block"
                aria-hidden="true"
              ></div>
    
              <ol class="grid gap-10 md:grid-cols-3 md:gap-8">
                @for (step of allSteps; track step.title; let index = $index) {
                  <li class="relative flex flex-col items-start md:items-center">
                    <div
                      class="border-border bg-card text-foreground relative z-10 flex size-12 items-center justify-center rounded-full border font-mono text-sm font-semibold"
                    >
                      {{ pad(index + 1) }}
                    </div>
    
                    <div class="mt-5 md:text-center">
                      <lucide-icon
                        [img]="step.icon"
                        class="text-muted-foreground mb-3 size-5 md:mx-auto"
                        aria-hidden="true"
                      />
                      <h3 class="text-base font-semibold">{{ step.title }}</h3>
                      <p class="text-muted-foreground mt-2 max-w-xs text-sm leading-relaxed md:mx-auto">{{ step.body }}</p>
                      <p class="text-muted-foreground/80 mt-3 font-mono text-xs tracking-wide uppercase">
                        {{ step.detail }}
                      </p>
                    </div>
                  </li>
                }
              </ol>
            </div>
    
            <div class="border-border mt-14 flex flex-wrap items-center gap-4 border-t pt-8">
              <button ui-button>Start the walkthrough</button>
              <button ui-button variant="ghost">Read the setup guide</button>
            </div>
          </div>
        </section>
      `,
    })
    export class UiHowItWorksStepsComponent {
      protected readonly allSteps = steps
    
      @Input('class') className?: string
    
      get rootClass(): string {
        return cn('bg-background', this.className)
      }
    
      pad(n: number): string {
        return String(n).padStart(2, '0')
      }
    }
    
  • src/app/components/blocks/how-it-works-steps/index.ts0.1 kB

Raw manifest:https://uipkge.dev/r/angular/how-it-works-steps.json