"use client";
import { ChevronDownIcon } from "lucide-react";
import { AnimatePresence, motion, useReducedMotion } from "motion/react";
import { useId, useState } from "react";
import { cn } from "@/lib/utils";
export interface LaunchTask {
id: string;
label: string;
hint?: string;
done?: boolean;
}
export interface LaunchGroup {
id: string;
title: string;
tasks: LaunchTask[];
}
export interface LaunchListProps {
title?: string;
description?: string;
groups: LaunchGroup[];
defaultOpen?: string[];
onToggleTask?: (taskId: string, done: boolean) => void;
className?: string;
}
const spring = { type: "spring", stiffness: 380, damping: 32 } as const;
function Ring({ value, total }: { value: number; total: number }) {
const reduce = useReducedMotion();
const r = 15;
const c = 2 * Math.PI * r;
const pct = total ? value / total : 0;
return (
{Math.round(pct * 100)}%
);
}
function TaskCheck({ done }: { done: boolean }) {
const reduce = useReducedMotion();
return (
);
}
export function LaunchList({
title = "Get set up",
description,
groups: initial,
defaultOpen,
onToggleTask,
className,
}: LaunchListProps) {
const reduce = useReducedMotion();
const baseId = useId();
const [groups, setGroups] = useState(initial);
const [open, setOpen] = useState(
() => defaultOpen ?? [initial.find((g) => g.tasks.some((t) => !t.done))?.id ?? initial[0]?.id].filter(Boolean),
);
const all = groups.flatMap((g) => g.tasks);
const done = all.filter((t) => t.done).length;
const toggle = (taskId: string) => {
const next = !all.find((t) => t.id === taskId)?.done;
setGroups((gs) =>
gs.map((g) => ({ ...g, tasks: g.tasks.map((t) => (t.id === taskId ? { ...t, done: next } : t)) })),
);
onToggleTask?.(taskId, next);
};
return (
{groups.map((g) => {
const isOpen = open.includes(g.id);
const gDone = g.tasks.filter((t) => t.done).length;
const complete = gDone === g.tasks.length;
const panelId = `${baseId}-${g.id}`;
return (
{isOpen && (
{g.tasks.map((t) => (
-
))}
)}
);
})}
);
}