Menu

Change language

GitHub repo

Onboarding Checklist

blockauth

Progressive activation checklist card: header with title, an N of M complete counter and a linear Progress bar, tappable task rows with icon tiles, one-line descriptions and chevrons (completed tasks flip to a filled check tile with muted copy), a footer with a Skip for now ghost link and an X dismiss that fades the card out, and a success panel with a glowing check circle at 5 of 5. State is local — wire tasks to your own activation API.

Installation

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

Variants

Loading interactive previews…

Schema

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

OnboardingChecklistTask
interface OnboardingChecklistTask {
  id: string
  title: string
  description: string
  icon: LucideIconData
  done: boolean
}

npm dependencies

Files installed (2)

  • src/app/components/blocks/onboarding-checklist/onboarding-checklist.component.ts8.2 kB
    // Edit this file to change the tasks and copy. State is local -- wire `toggle()` and
    // `dismiss()` to your own activation API.
    import { Component, Input, OnInit, booleanAttribute, computed, signal, ChangeDetectionStrategy } from '@angular/core'
    import {
      Bell,
      BookOpen,
      Building2,
      Check,
      ChevronRight,
      Database,
      LucideAngularModule,
      Users,
      X,
      type LucideIconData,
    } from 'lucide-angular'
    import { cn } from '@/lib/utils'
    import { UiButtonComponent } from '@/ui/button/button.component'
    import {
      UiCardComponent,
      UiCardContentComponent,
      UiCardDescriptionComponent,
      UiCardFooterComponent,
      UiCardHeaderComponent,
      UiCardTitleComponent,
    } from '@/ui/card/card.component'
    import { UiProgressComponent } from '@/ui/progress/progress.component'
    
    export interface OnboardingChecklistTask {
      id: string
      title: string
      description: string
      icon: LucideIconData
      done: boolean
    }
    
    const baseTasks: Omit<OnboardingChecklistTask, 'done'>[] = [
      {
        id: 'workspace',
        title: 'Create workspace',
        description: 'Name your team space and pick a URL.',
        icon: Building2,
      },
      {
        id: 'teammates',
        title: 'Invite teammates',
        description: 'Bring in the people you work with.',
        icon: Users,
      },
      {
        id: 'data',
        title: 'Connect your data',
        description: 'Sync sources so insights flow in automatically.',
        icon: Database,
      },
      {
        id: 'alerts',
        title: 'Set up alerts',
        description: 'Get notified when metrics move.',
        icon: Bell,
      },
      {
        id: 'api',
        title: 'Explore API docs',
        description: 'Learn the endpoints and ship your first request.',
        icon: BookOpen,
      },
    ]
    
    /**
     * OnboardingChecklist -- the React block, 1:1: a card with an "N of M complete" counter and
     * a progress bar over tappable task rows (a done task flips to a filled check tile with muted
     * copy). "Skip for now" and the X fade the card out; at 5 of 5 the rows become a success panel.
     */
    @Component({
      changeDetection: ChangeDetectionStrategy.Eager,
      selector: 'ui-onboarding-checklist, [ui-onboarding-checklist]',
      standalone: true,
      // React renders the root <div> itself: the host stays out of layout and `class` goes to the root.
      host: { '[attr.class]': '"contents"' },
      imports: [
        LucideAngularModule,
        UiButtonComponent,
        UiCardComponent,
        UiCardHeaderComponent,
        UiCardTitleComponent,
        UiCardDescriptionComponent,
        UiCardContentComponent,
        UiCardFooterComponent,
        UiProgressComponent,
      ],
      template: `
        <div data-slot="onboarding-checklist" [class]="rootClass">
          <ui-card>
            <ui-card-header>
              <ui-card-title>Get started</ui-card-title>
              <ui-card-description>Finish these steps to unlock everything your workspace can do.</ui-card-description>
            </ui-card-header>
            <ui-card-content class="space-y-4">
              <div class="space-y-2">
                <div class="flex items-center justify-between">
                  <p class="text-muted-foreground text-xs">
                    <span class="text-foreground font-medium">{{ completed() }} of {{ total() }}</span> complete
                  </p>
                  <p class="text-muted-foreground text-xs tabular-nums">{{ percent() }}%</p>
                </div>
                <ui-progress [value]="percent()" />
              </div>
    
              @if (allDone()) {
                <div class="space-y-4 py-6 text-center">
                  <div
                    class="border-success/30 bg-success/10 text-success mx-auto flex size-16 items-center justify-center rounded-full border shadow-xs"
                  >
                    <lucide-icon [img]="Check" class="size-8" aria-hidden="true" />
                  </div>
                  <div>
                    <p class="text-lg font-semibold">You're all set</p>
                    <p class="text-muted-foreground mt-1 text-sm">
                      Every step is complete — your workspace is ready to go.
                    </p>
                  </div>
                </div>
              } @else {
                <div class="-mx-2 divide-y">
                  @for (task of tasks(); track task.id) {
                    <button
                      type="button"
                      role="checkbox"
                      [attr.aria-checked]="task.done"
                      class="group hover:bg-muted/50 focus-visible:ring-ring flex w-full items-center gap-3 rounded-lg p-3 text-left transition-colors focus-visible:ring-2 focus-visible:outline-none"
                      (click)="toggle(task.id)"
                    >
                      @if (task.done) {
                        <span
                          class="bg-primary text-primary-foreground flex size-9 shrink-0 items-center justify-center rounded-md shadow-xs"
                        >
                          <lucide-icon [img]="Check" class="size-4" aria-hidden="true" />
                        </span>
                      } @else {
                        <span
                          class="border-border bg-muted text-muted-foreground group-hover:bg-background group-hover:text-foreground flex size-9 shrink-0 items-center justify-center rounded-md border transition-colors"
                        >
                          <lucide-icon [img]="task.icon" class="size-4" aria-hidden="true" />
                        </span>
                      }
                      <span class="min-w-0 flex-1">
                        <span [class]="titleClass(task.done)">{{ task.title }}</span>
                        <span class="text-muted-foreground mt-0.5 block truncate text-xs">{{ task.description }}</span>
                      </span>
                      <!-- [class] (not class=) feeds only the svg, so the hover nudge is not applied twice (host + svg). -->
                      <lucide-icon
                        [img]="ChevronRight"
                        [class]="'text-muted-foreground/50 size-4 shrink-0 transition-transform duration-200 group-hover:translate-x-0.5'"
                        aria-hidden="true"
                      />
                    </button>
                  }
                </div>
              }
            </ui-card-content>
            <ui-card-footer class="justify-between">
              @if (!allDone()) {
                <button ui-button variant="ghost" size="sm" (click)="dismiss()">Skip for now</button>
              } @else {
                <span aria-hidden="true"></span>
              }
              <button
                type="button"
                aria-label="Dismiss checklist"
                class="hover:bg-foreground/10 focus-visible:ring-ring rounded-md p-1.5 transition-colors focus-visible:ring-2 focus-visible:outline-none"
                (click)="dismiss()"
              >
                <lucide-icon [img]="X" class="text-muted-foreground size-4" aria-hidden="true" />
              </button>
            </ui-card-footer>
          </ui-card>
        </div>
      `,
    })
    export class UiOnboardingChecklistComponent implements OnInit {
      /** Seed every task as done so the success panel can be previewed in isolation. */
      @Input({ transform: booleanAttribute }) initialComplete = false
      @Input('class') className?: string
    
      readonly tasks = signal<OnboardingChecklistTask[]>(this.seed())
      readonly open = signal(true)
    
      readonly completed = computed(() => this.tasks().filter((task) => task.done).length)
      readonly total = computed(() => this.tasks().length)
      readonly percent = computed(() => Math.round((this.completed() / this.total()) * 100))
      readonly allDone = computed(() => this.completed() === this.total())
    
      protected get rootClass(): string {
        return cn(
          'mx-auto w-full max-w-md transition-[opacity,translate] duration-200 ease-out motion-reduce:transition-none',
          !this.open() && 'pointer-events-none translate-y-1 opacity-0',
          this.className,
        )
      }
    
      protected readonly Check = Check
      protected readonly ChevronRight = ChevronRight
      protected readonly X = X
    
      ngOnInit(): void {
        // React seeds its useState once from `initialComplete`; inputs land after construction.
        this.tasks.set(this.seed())
      }
    
      toggle(id: string): void {
        this.tasks.update((prev) => prev.map((task) => (task.id === id ? { ...task, done: !task.done } : task)))
      }
    
      dismiss(): void {
        this.open.set(false)
      }
    
      protected titleClass(done: boolean): string {
        return cn('block truncate text-sm font-medium', done && 'text-muted-foreground')
      }
    
      private seed(): OnboardingChecklistTask[] {
        return baseTasks.map((task, index) => ({ ...task, done: this.initialComplete || index < 3 }))
      }
    }
    
  • src/app/components/blocks/onboarding-checklist/index.ts0.1 kB

Raw manifest:https://uipkge.dev/r/angular/onboarding-checklist.json