111 lines
3.5 KiB
TypeScript
111 lines
3.5 KiB
TypeScript
import { Link, useNavigate } from "@tanstack/react-router";
|
|
import { useQuery } from "@tanstack/react-query";
|
|
import { Check, ChevronsUpDown, FolderCog } from "lucide-react";
|
|
import { getProjects } from "@/serverFunctions/projects";
|
|
import { setLastProjectId } from "@/client/lib/active-project";
|
|
import { closeDropdown } from "@/client/lib/dropdown";
|
|
import type { ProjectSummary } from "./types";
|
|
|
|
export function ProjectSwitcher({
|
|
activeProjectId,
|
|
onCloseDrawer,
|
|
}: {
|
|
activeProjectId: string | null;
|
|
// Mobile sidebar passes this so switching / navigating away also closes the
|
|
// drawer overlay.
|
|
onCloseDrawer?: () => void;
|
|
}) {
|
|
const navigate = useNavigate();
|
|
const projectsQuery = useQuery({
|
|
queryKey: ["projects"],
|
|
queryFn: () => getProjects(),
|
|
});
|
|
const projects = projectsQuery.data ?? [];
|
|
const activeProject =
|
|
projects.find((project) => project.id === activeProjectId) ?? null;
|
|
|
|
const handleSelect = (project: ProjectSummary) => {
|
|
closeDropdown();
|
|
onCloseDrawer?.();
|
|
if (project.id === activeProjectId) return;
|
|
setLastProjectId(project.id);
|
|
void navigate({
|
|
to: "/p/$projectId",
|
|
params: { projectId: project.id },
|
|
});
|
|
};
|
|
|
|
return (
|
|
<div className="dropdown w-full">
|
|
<button
|
|
type="button"
|
|
tabIndex={0}
|
|
aria-label="Switch project"
|
|
className="flex w-full items-center justify-between gap-2 rounded-lg border border-base-300 bg-base-100 px-3 py-1.5 text-left transition-colors hover:border-base-content/25"
|
|
>
|
|
<span className="flex min-w-0 flex-col">
|
|
<span className="truncate text-sm font-medium text-base-content">
|
|
{activeProject?.name ?? "Select project"}
|
|
</span>
|
|
{activeProject?.domain ? (
|
|
<span className="truncate text-xs font-normal text-base-content/50">
|
|
{activeProject.domain}
|
|
</span>
|
|
) : null}
|
|
</span>
|
|
<ChevronsUpDown className="size-3.5 shrink-0 text-base-content/40" />
|
|
</button>
|
|
|
|
<ul
|
|
tabIndex={0}
|
|
className="dropdown-content z-30 menu w-full rounded-box border border-base-300 bg-base-100 p-2 shadow-lg"
|
|
>
|
|
{projects.map((project) => {
|
|
const isActive = project.id === activeProjectId;
|
|
return (
|
|
<li key={project.id}>
|
|
<button
|
|
type="button"
|
|
onClick={() => handleSelect(project)}
|
|
className={isActive ? "active" : ""}
|
|
>
|
|
<span className="flex min-w-0 flex-1 flex-col">
|
|
<span className="truncate">{project.name}</span>
|
|
{project.domain ? (
|
|
<span className="truncate text-xs text-base-content/50">
|
|
{project.domain}
|
|
</span>
|
|
) : null}
|
|
</span>
|
|
{isActive ? (
|
|
<Check className="size-4 shrink-0 text-primary" />
|
|
) : null}
|
|
</button>
|
|
</li>
|
|
);
|
|
})}
|
|
|
|
{projects.length > 0 ? (
|
|
<li
|
|
aria-hidden="true"
|
|
className="pointer-events-none my-1 h-px bg-base-300 p-0"
|
|
/>
|
|
) : null}
|
|
|
|
<li>
|
|
<Link
|
|
to="/projects"
|
|
onClick={() => {
|
|
closeDropdown();
|
|
onCloseDrawer?.();
|
|
}}
|
|
>
|
|
<FolderCog className="size-4" />
|
|
Manage projects
|
|
</Link>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
);
|
|
}
|