"use client"; import { useEffect, useId, useRef, useState, type CSSProperties, type ReactNode } from "react"; import "./quickstart-checklist.css"; export type QuickstartStep = { id: string; title: string; body: ReactNode; /** A primary action for this step, e.g. opening a dialog. */ action?: { label: string; onClick: () => void }; }; export type QuickstartChecklistProps = { title?: string; steps: QuickstartStep[]; /** Remember finished steps across visits under this key. */ storageKey?: string; onComplete?: () => void; theme?: "light" | "dark"; className?: string; }; /** * Quickstart Checklist * Onboarding as a short stack of numbered cards. The next unfinished step is * open; marking it done stamps it, folds it away and opens the one after. * A segmented bar keeps count. */ export function QuickstartChecklist({ title = "Get set up", steps, storageKey, onComplete, theme = "light", className = "" }: QuickstartChecklistProps) { const id = useId(); const [done, setDone] = useState([]); const [open, setOpen] = useState(steps[0]?.id ?? null); const heads = useRef(new Map()); const reported = useRef(false); useEffect(() => { if (!storageKey) return; try { const saved = JSON.parse(localStorage.getItem(storageKey) ?? "[]"); if (Array.isArray(saved)) { const valid = saved.filter((s) => steps.some((x) => x.id === s)); setDone(valid); setOpen(steps.find((s) => !valid.includes(s.id))?.id ?? null); reported.current = valid.length === steps.length; } } catch { /* storage unavailable or corrupt */ } }, [storageKey, steps]); const save = (next: string[]) => { setDone(next); if (storageKey) try { localStorage.setItem(storageKey, JSON.stringify(next)); } catch { /* storage unavailable */ } if (next.length === steps.length && !reported.current) { reported.current = true; onComplete?.(); } if (next.length < steps.length) reported.current = false; }; const finish = (stepId: string) => { const next = [...done.filter((d) => d !== stepId), stepId]; save(next); const following = steps.find((s) => !next.includes(s.id)); setOpen(following?.id ?? null); requestAnimationFrame(() => heads.current.get(following?.id ?? stepId)?.focus()); }; const undo = (stepId: string) => { save(done.filter((d) => d !== stepId)); setOpen(stepId); }; const count = done.length; return (

{title}

{count === steps.length ? "All done" : `${count} of ${steps.length} done`}

{steps.map((s) => )}
    {steps.map((step, i) => { const isDone = done.includes(step.id); const isOpen = open === step.id; return (
  1. ); })}
); }