121 lines
5.0 KiB
TypeScript
121 lines
5.0 KiB
TypeScript
import { ChevronRight, Loader2, Trash2 } from "lucide-react";
|
|
import type { Ref } from "react";
|
|
import type { CanvasProject } from "./types";
|
|
import { formatRelativeTime } from "./utils";
|
|
|
|
type LovartProjectsSectionProps = {
|
|
deletingProjectId: string | null;
|
|
hasMoreProjects: boolean;
|
|
isProjectsOnlyView: boolean;
|
|
onCreateProject: () => void;
|
|
onDeleteProject: (projectId: string, projectTitle?: string) => void;
|
|
onOpenProject: (projectId: string) => void;
|
|
onPrepareCreate: () => void;
|
|
onPrepareProject: (projectId: string) => void;
|
|
onViewAllProjects: () => void;
|
|
projects: CanvasProject[];
|
|
projectSectionRef: Ref<HTMLElement>;
|
|
};
|
|
|
|
export function LovartProjectsSection({
|
|
deletingProjectId,
|
|
hasMoreProjects,
|
|
isProjectsOnlyView,
|
|
onCreateProject,
|
|
onDeleteProject,
|
|
onOpenProject,
|
|
onPrepareCreate,
|
|
onPrepareProject,
|
|
onViewAllProjects,
|
|
projects,
|
|
projectSectionRef,
|
|
}: LovartProjectsSectionProps) {
|
|
return (
|
|
<section
|
|
ref={projectSectionRef}
|
|
className={isProjectsOnlyView ? "mt-5" : "mt-[112px]"}
|
|
>
|
|
<div className="mb-6 flex items-center justify-between">
|
|
<h2 className="text-[20px] font-semibold leading-none text-[#1f1f1f] dark:text-white">
|
|
{isProjectsOnlyView ? "所有项目" : "最近项目"}
|
|
</h2>
|
|
{!isProjectsOnlyView && hasMoreProjects ? (
|
|
<button
|
|
type="button"
|
|
className="inline-flex items-center gap-1 text-[13px] font-normal leading-none text-[#7d7d7d] transition-colors duration-150 hover:text-[#1f1f1f] dark:text-white/50 dark:hover:text-white"
|
|
onClick={onViewAllProjects}
|
|
>
|
|
查看全部
|
|
<ChevronRight className="h-3.5 w-3.5" strokeWidth={1.8} />
|
|
</button>
|
|
) : null}
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 items-start gap-x-4 gap-y-7 md:grid-cols-2 lg:grid-cols-5">
|
|
<button
|
|
type="button"
|
|
onClick={onCreateProject}
|
|
onFocus={onPrepareCreate}
|
|
onPointerEnter={onPrepareCreate}
|
|
className="group block w-full cursor-pointer appearance-none bg-transparent p-0 text-left"
|
|
>
|
|
<div className="flex aspect-[16/9] items-center justify-center rounded-[8px] border border-dashed border-[#d8d8d8] bg-[#f4f4f4] transition-colors duration-150 group-hover:border-[#c8c8c8] group-hover:bg-[#f0f0f0] dark:border-white/15 dark:bg-[#18191d]">
|
|
<span className="text-[26px] font-light leading-none text-[#8e8e8e]">+</span>
|
|
</div>
|
|
<div className="mt-3 text-[14px] font-normal leading-none text-[#202020] dark:text-white">
|
|
新建项目
|
|
</div>
|
|
</button>
|
|
|
|
{projects.map((item) => (
|
|
<div
|
|
key={item.id}
|
|
className="group relative text-left"
|
|
>
|
|
<button
|
|
type="button"
|
|
onClick={() => onOpenProject(item.id)}
|
|
onFocus={() => onPrepareProject(item.id)}
|
|
onPointerEnter={() => onPrepareProject(item.id)}
|
|
className="block w-full cursor-pointer text-left"
|
|
>
|
|
<div className="aspect-[16/9] overflow-hidden rounded-[8px] border border-[#dddddd] bg-[#f4f4f4] dark:border-white/10 dark:bg-[#1b1c20]">
|
|
{item.thumbnail ? (
|
|
<img
|
|
src={item.thumbnail}
|
|
alt={item.title}
|
|
loading="lazy"
|
|
decoding="async"
|
|
className="h-full w-full object-cover transition-transform duration-150 ease-out will-change-transform group-hover:scale-[1.018]"
|
|
/>
|
|
) : (
|
|
<div className="h-full w-full" />
|
|
)}
|
|
</div>
|
|
<div className="mt-3 truncate text-[14px] font-normal leading-none text-[#202020] dark:text-white">
|
|
{item.title || "Untitled"}
|
|
</div>
|
|
<div className="mt-2 text-[11px] font-normal leading-none text-[#858585] dark:text-white/42">
|
|
{formatRelativeTime(item.updatedAt || item.createdAt)}
|
|
</div>
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onClick={() => onDeleteProject(item.id, item.title)}
|
|
disabled={deletingProjectId === item.id}
|
|
className="absolute right-3 top-3 z-20 inline-flex h-8 w-8 items-center justify-center rounded-full border border-black/10 bg-white/90 text-black/45 opacity-0 shadow-sm transition hover:border-[#ffb4b4] hover:text-[#ef4444] group-hover:opacity-100 disabled:cursor-not-allowed disabled:opacity-80 dark:border-white/10 dark:bg-[#18181c]/90 dark:text-white/45"
|
|
aria-label={`删除${item.title || "未命名画布"}`}
|
|
>
|
|
{deletingProjectId === item.id ? (
|
|
<Loader2 className="h-3.5 w-3.5 animate-spin" />
|
|
) : (
|
|
<Trash2 className="h-3.5 w-3.5" />
|
|
)}
|
|
</button>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|