nyuchimzizi
Mzizi — an open-architecture project of the Bundu Foundation, operated and developed by Nyuchi. Built on the Five African Minerals palette.
Built by Nyuchi Africav4.0.39
Step-by-step spotlight overlay guiding users through UI features.
View the full component source code below.
"use client"
import { useNyuchiHarness } from "@/lib/harness"
import * as React from "react"
import { X } from "@/lib/icons"
import { cn } from "@/lib/utils"
interface TourStep {
target: string
title: string
description: string
position?: "top" | "bottom" | "left" | "right"
}
function OnboardingTour({
steps,
active,
onNext,
onSkip,
loading = false, className,
...props
}: {
steps: TourStep[]
active: number
onNext: () => void
onSkip: () => void
} & React.ComponentProps<"div">) {
const { log, motion, LiveRegion } = useNyuchiHarness("onboarding-tour")
const animStyle = React.useMemo(() => motion.prefersReduced ? {} : { animation: `nyuchi-fade-slide-up ${motion.enterDuration}ms ${motion.enterEasing} both` }, [motion])
const [rect, setRect] = React.useState<DOMRect | null>(null)
const step = steps[active]
React.useEffect(() => {
if (!step) return
const el = document.querySelector(step.target)
if (el) {
const r = el.getBoundingClientRect()
setRect(r)
el.scrollIntoView({ behavior: "smooth", block: "center" })
} else {
setRect(null)
}
}, [step])
if (!step || active >= steps.length) return null
const position = step.position ?? "bottom"
const padding = 8
const tooltipStyle: React.CSSProperties = rect
? {
position: "fixed",
...(position === "bottom" && {
top: rect.bottom + padding,
left: rect.left + rect.width / 2,
transform: "translateX(-50%)",
}),
...(position === "top" && {
bottom: window.innerHeight - rect.top + padding,
left: rect.left + rect.width / 2,
transform: "translateX(-50%)",
}),
...(position === "left" && {
top: rect.top + rect.height / 2,
right: window.innerWidth - rect.left + padding,
transform: "translateY(-50%)",
}),
...(position === "right" && {
top: rect.top + rect.height / 2,
left: rect.right + padding,
transform: "translateY(-50%)",
}),
}
: { position: "fixed", top: "50%", left: "50%", transform: "translate(-50%, -50%)" }
return (
<div data-slot="onboarding-tour" data-portal="https://design.nyuchi.com/components/onboarding-tour" className={cn("", className)} {...props}>
{/* Overlay */}
<div className="fixed inset-0 z-[9998] bg-foreground/40" onClick={onSkip} />
{/* Spotlight cutout */}
{rect && (
<div
className="fixed z-[9999] rounded-lg ring-[9999px] ring-foreground/40"
style={{
top: rect.top - padding,
left: rect.left - padding,
width: rect.width + padding * 2,
height: rect.height + padding * 2,
}}
/>
)}
{/* Tooltip */}
<div
className="z-[10000] w-72 rounded-xl border border-border bg-card p-4 shadow-lg"
style={tooltipStyle}
>
<div className="flex items-start justify-between gap-2">
<h4 className="text-sm font-medium text-foreground">{step.title}</h4>
<button
type="button"
onClick={onSkip}
className="shrink-0 p-0.5 text-muted-foreground transition-colors hover:text-foreground"
aria-label="Skip tour"
>
<X className="size-4" />
</button>
</div>
<p className="mt-1 text-xs text-muted-foreground">{step.description}</p>
<div className="mt-3 flex items-center justify-between">
<span className="text-xs text-muted-foreground tabular-nums">
{active + 1} of {steps.length}
</span>
<div className="flex gap-2">
<button
type="button"
onClick={onSkip}
className="rounded-lg px-3 py-1 text-xs font-medium text-muted-foreground transition-colors hover:text-foreground"
>
Skip
</button>
<button
type="button"
onClick={onNext}
className="rounded-4xl bg-[var(--color-primary, var(--color-cobalt, #00B0FF))] px-3 py-1 text-xs font-medium text-white transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--color-primary,#00B0FF)] hover:bg-[var(--color-primary, var(--color-cobalt, #00B0FF))]/90"
>
{active === steps.length - 1 ? "Done" : "Next"}
</button>
</div>
</div>
</div>
</div>
)
}
export { OnboardingTour, type TourStep }
npx shadcn@latest add https://mzizi.dev/api/v1/ui/onboarding-tourFetch this component's metadata and source code from the registry API.
/api/v1/ui/onboarding-tourcomponents/ui/onboarding-tour.tsx