Demo — Guided Tour
blockmarketingStep-through product tour where next and previous move a highlight across a mocked interface, with a step counter and a skip control.
Installation
$pnpm dlx uipkge-ng@latest add demo-guided-tour$npx uipkge-ng@latest add demo-guided-tour$yarn dlx uipkge-ng@latest add demo-guided-tour$bunx uipkge-ng@latest add demo-guided-tourManifest:
https://uipkge.dev/r/angular/demo-guided-tour.jsonInstalls to:src/app/components/blocks/demo-guided-tour/Variants
Loading interactive previews…
Loading interactive previews…
Schema
Type aliases exported from this item's source. Use these to shape the data you pass in.
GuidedTourStepinterface GuidedTourStep {
target: string
title: string
body: string
}GuidedTourPanelinterface GuidedTourPanel {
id: string
label: string
rows: string[]
}Files installed (2)
src/app/components/blocks/demo-guided-tour/demo-guided-tour.component.ts6 kB
// Step-through product tour where next and previous move a highlight across a mocked // interface, with a step counter, progress bar and a skip control. Port of the React // block 1:1. import { ChangeDetectionStrategy, Component, Input, computed, signal } from '@angular/core' import { ArrowLeft, ArrowRight, LucideAngularModule } 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 { UiProgressComponent } from '@/ui/progress/progress.component' import { UiSeparatorComponent } from '@/ui/separator/separator.component' export interface GuidedTourStep { target: string title: string body: string } export interface GuidedTourPanel { id: string label: string rows: string[] } // Each step names the panel it highlights, so the copy and the mock cannot // drift apart when someone reorders the tour. export const GUIDED_TOUR_STEPS: GuidedTourStep[] = [ { target: 'catalog', title: 'Start from the catalogue', body: 'Every table we mirrored, with its freshness. Pick what the metric reads from.', }, { target: 'definition', title: 'Write the definition', body: 'The window, the owner, and the joins. This is the file that opens a pull request.', }, { target: 'consumers', title: 'See who it affects', body: 'Before publishing, the consumer list shows every dashboard and export that resolves it.', }, { target: 'history', title: 'Check the history', body: 'Every prior version with its reviewer, and a one-click revert to any of them.', }, ] export const GUIDED_TOUR_PANELS: GuidedTourPanel[] = [ { id: 'catalog', label: 'Catalogue', rows: ['warehouse.orders', 'warehouse.refunds', 'warehouse.fx_rates'] }, { id: 'definition', label: 'revenue_net.yml', rows: ['window: trailing_28d', 'owner: finance-analytics', 'joins: refunds, fx'], }, { id: 'consumers', label: 'Consumers', rows: ['Exec summary', 'Finance close pack', 'Partner API'] }, { id: 'history', label: 'History', rows: ['v128 · 14 Feb · A. Reyes', 'v127 · 02 Jan · A. Reyes', 'v126 · 11 Dec · M. Ellery'], }, ] @Component({ changeDetection: ChangeDetectionStrategy.Eager, selector: 'ui-demo-guided-tour, [ui-demo-guided-tour]', standalone: true, host: { '[attr.data-slot]': '"demo-guided-tour"', '[class]': 'hostClass', }, imports: [ LucideAngularModule, UiBadgeComponent, UiButtonComponent, UiCardComponent, UiCardContentComponent, UiProgressComponent, UiSeparatorComponent, ], template: ` <div class="mx-auto max-w-5xl 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">Guided tour</span> <h2 class="mt-4 text-3xl font-semibold tracking-tight sm:text-4xl">Four screens, ninety seconds</h2> </div> <button ui-button variant="ghost" size="sm">Skip and just read the docs</button> </div> <div class="mt-10 grid gap-6 lg:grid-cols-[1fr_1.3fr] lg:gap-10"> <div> <div ui-progress [value]="progress()" class="h-1" aria-label="Tour progress"></div> <p class="text-muted-foreground mt-3 font-mono text-xs">Step {{ index() + 1 }} of {{ steps.length }}</p> <!-- min-h holds the copy block at its tallest so the controls do not move between steps. --> <div class="min-h-[7rem]"> <h3 class="mt-4 text-xl font-semibold tracking-tight">{{ current().title }}</h3> <p class="text-muted-foreground mt-2 leading-relaxed">{{ current().body }}</p> </div> <div class="mt-5 flex items-center gap-2"> <button ui-button variant="outline" size="sm" [disabled]="index() === 0" (click)="back()"> <lucide-icon [img]="ArrowLeft" class="mr-1.5 size-3.5" /> Back </button> <button ui-button size="sm" [disabled]="index() === steps.length - 1" (click)="next()"> Next <lucide-icon [img]="ArrowRight" class="ml-1.5 size-3.5" /> </button> </div> </div> <div ui-card> <div ui-card-content class="space-y-2 p-4"> @for (panel of panels; track panel.id) { <div class="rounded-lg border p-3 transition-colors" [class]="panelClass(panel.id)"> <div class="flex items-center justify-between gap-3"> <p class="font-mono text-xs font-medium">{{ panel.label }}</p> @if (panel.id === current().target) { <span ui-badge variant="secondary">This step</span> } </div> <ui-separator class="my-2" /> <ul class="space-y-1"> @for (row of panel.rows; track row) { <li class="text-muted-foreground truncate font-mono text-xs"> {{ row }} </li> } </ul> </div> } </div> </div> </div> </div> `, }) export class UiDemoGuidedTourComponent { protected readonly ArrowLeft = ArrowLeft protected readonly ArrowRight = ArrowRight @Input('class') className?: string readonly steps = GUIDED_TOUR_STEPS readonly panels = GUIDED_TOUR_PANELS readonly index = signal(0) readonly current = computed(() => this.steps[this.index()] ?? this.steps[0]!) readonly progress = computed(() => ((this.index() + 1) / this.steps.length) * 100) get hostClass(): string { return cn('bg-background block w-full', this.className) } panelClass(id: string): string { return cn(id === this.current().target ? 'border-primary bg-muted/40' : 'border-border bg-background opacity-60') } next(): void { this.index.update((i) => Math.min(i + 1, this.steps.length - 1)) } back(): void { this.index.update((i) => Math.max(i - 1, 0)) } }src/app/components/blocks/demo-guided-tour/index.ts0.2 kB
export { UiDemoGuidedTourComponent, GUIDED_TOUR_STEPS, GUIDED_TOUR_PANELS, type GuidedTourStep, type GuidedTourPanel, } from './demo-guided-tour.component'
Raw manifest:https://uipkge.dev/r/angular/demo-guided-tour.json