"use client"; import { ArrowRightIcon, BookOpenIcon, BugIcon, FolderKanbanIcon, InboxIcon, LayersIcon, type LucideIcon, MapIcon, PlusIcon, RocketIcon, SearchIcon, SquareCheckIcon, UsersIcon, } from "lucide-react"; import { AnimatePresence, LayoutGroup, motion, useReducedMotion } from "motion/react"; import { type KeyboardEvent, useId, useState } from "react"; import { Button } from "@/components/ui/button"; import { Kbd } from "@/components/ui/kbd"; import { cn } from "@/lib/utils"; export interface ProjectTemplate { id: string; name: string; description: string; icon: LucideIcon; issues: number; tone: "violet" | "amber" | "sky" | "emerald" | "rose"; } export interface ProjectRow { id: string; name: string; issues: number; icon: LucideIcon; tone?: ProjectTemplate["tone"]; templateId?: string; } export interface FirstProjectProps { workspace?: string; user?: { name: string; initials: string }; templates?: ProjectTemplate[]; shortcut?: string; defaultProjects?: ProjectRow[]; onCreate?: (template?: ProjectTemplate) => void; onOpenGuide?: () => void; className?: string; } const defaultTemplates: ProjectTemplate[] = [ { id: "launch", name: "Product launch", description: "Milestones from beta to GA, with a launch-day checklist.", icon: RocketIcon, issues: 14, tone: "violet", }, { id: "bugbash", name: "Bug bash", description: "A one-week sweep with triage, owners and a fix-by date.", icon: BugIcon, issues: 8, tone: "amber", }, { id: "roadmap", name: "Quarterly roadmap", description: "Themes, bets and the issues that move each one forward.", icon: MapIcon, issues: 6, tone: "sky", }, ]; const tones: Record = { violet: "bg-violet-500/12 text-violet-700 dark:text-violet-300", amber: "bg-amber-500/14 text-amber-700 dark:text-amber-300", sky: "bg-sky-500/12 text-sky-700 dark:text-sky-300", emerald: "bg-emerald-500/12 text-emerald-700 dark:text-emerald-300", rose: "bg-rose-500/12 text-rose-700 dark:text-rose-300", }; const nav: { label: string; icon: LucideIcon; active?: boolean }[] = [ { label: "Inbox", icon: InboxIcon }, { label: "My issues", icon: SquareCheckIcon }, { label: "Projects", icon: FolderKanbanIcon, active: true }, { label: "Views", icon: LayersIcon }, { label: "Members", icon: UsersIcon }, ]; const ease = [0.23, 1, 0.32, 1] as const; const spring = { type: "spring", stiffness: 380, damping: 34, mass: 0.9 } as const; function Mark({ className }: { className?: string }) { return ( ); } function Count({ value }: { value: number }) { const reduce = useReducedMotion(); return ( {value} ); } function ProgressRing({ value }: { value: number }) { const r = 6; const c = 2 * Math.PI * r; return ( ); } export function FirstProject({ workspace = "Ridgeline", user = { name: "Ada Lindqvist", initials: "AL" }, templates = defaultTemplates, shortcut = "C", defaultProjects = [], onCreate, onOpenGuide, className, }: FirstProjectProps) { const reduce = useReducedMotion(); const uid = useId(); const [projects, setProjects] = useState(defaultProjects); const [editing, setEditing] = useState(null); const [fresh, setFresh] = useState(null); const empty = projects.length === 0; const tileId = `${uid}-tile`; const create = (template?: ProjectTemplate) => { const id = `${template?.id ?? "blank"}-${projects.length + 1}`; setProjects((list) => [ { id, name: template?.name ?? "", issues: template?.issues ?? 0, icon: template?.icon ?? FolderKanbanIcon, tone: template?.tone, templateId: empty ? template?.id : undefined, }, ...list, ]); setEditing(template ? null : id); setFresh(id); onCreate?.(template); }; const rename = (id: string, name: string) => { setProjects((list) => list.map((p) => (p.id === id ? { ...p, name: name.trim() || "Untitled project" } : p))); setEditing(null); }; const onKeyDown = (e: KeyboardEvent) => { if (e.metaKey || e.ctrlKey || e.altKey || e.repeat) return; if ((e.target as HTMLElement).closest("input, textarea, [contenteditable=true]")) return; if (e.key.toLowerCase() !== shortcut.toLowerCase()) return; e.preventDefault(); create(); }; const layout = reduce ? { duration: 0 } : { type: "spring" as const, stiffness: 300, damping: 30 }; return (

Projects

{empty ? (

Plan your first project

Projects group issues around one outcome, like a launch or a migration, so the team can see what is left and when it ships.

Or start from a template
{templates.map((t) => ( ))}
) : (
Name Lead Issues Progress
    {projects.map((p, i) => { const fromTile = i === projects.length - 1; const id = p.templateId ? `${uid}-tpl-${p.templateId}` : fromTile ? tileId : undefined; return ( {fresh === p.id && ( )} {editing === p.id ? ( rename(p.id, e.currentTarget.value)} onKeyDown={(e) => { if (e.key === "Enter" || e.key === "Escape") e.currentTarget.blur(); }} placeholder="Name your project" /> ) : ( {p.name || "Untitled project"} )} {user.initials} {user.name.split(" ")[0]} {p.issues} 0% ); })}
{projects.length === 1 && ( Next, add issues to it, or press {shortcut.toUpperCase()} for another project. )}
)}
); }