Files
gitlab-instance-0a899031_do…/components/apps-explorer.tsx
2026-04-02 07:38:11 -05:00

114 lines
5.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { useMemo, useState } from "react";
import Link from "next/link";
import { CategoryTabs } from "@/components/sections";
import { AppResource } from "@/lib/types";
export function AppsExplorer({ apps }: { apps: AppResource[] }) {
const [query, setQuery] = useState("");
const [category, setCategory] = useState("all");
const [sort, setSort] = useState("latest");
const [count, setCount] = useState(6);
const filtered = useMemo(() => {
let list = [...apps].filter((a) =>
[a.name, a.subtitle, ...a.tags].join(" ").toLowerCase().includes(query.toLowerCase())
);
if (category !== "all") list = list.filter((a) => a.category === category);
list.sort((a, b) => {
if (sort === "hot") return b.downloads - a.downloads;
if (sort === "update") return b.updatedAt.localeCompare(a.updatedAt);
return b.uploadedAt.localeCompare(a.uploadedAt);
});
return list;
}, [apps, query, category, sort]);
return (
<section className="space-y-4 md:space-y-5">
<div className="panel space-y-4">
<div className="grid gap-3 md:grid-cols-12 md:items-center">
<div className="md:col-span-8">
<input
className="input-fancy"
placeholder="搜索资源、标签或名称"
value={query}
onChange={(e) => setQuery(e.target.value)}
/>
</div>
<div className="flex items-center justify-between rounded-xl border border-white/10 px-3 py-2 text-xs text-slate-300 md:col-span-4 md:text-sm">
<span></span>
<strong className="text-cyan-200">{filtered.length}</strong>
</div>
</div>
<CategoryTabs active={category} onChange={setCategory} />
<div className="flex flex-wrap items-center gap-2 text-sm">
<span className="text-slate-300"></span>
{[
{ id: "latest", label: "最新上架" },
{ id: "hot", label: "热度优先" },
{ id: "update", label: "最近更新" },
].map((s) => (
<button key={s.id} className={`pill ${sort === s.id ? "pill-active" : ""}`} onClick={() => setSort(s.id)}>
{s.label}
</button>
))}
</div>
</div>
{filtered.length > 0 ? (
<div className="space-y-3">
{filtered.slice(0, count).map((app) => (
<div key={app.id} className="panel-strong flex min-w-0 flex-col gap-3 md:flex-row md:items-center md:justify-between">
<div className="flex min-w-0 flex-1 items-start gap-3">
<span className="grid h-10 w-10 shrink-0 place-items-center rounded-xl border border-white/10 bg-slate-900/50 text-lg">{app.icon}</span>
<div className="min-w-0 flex-1">
<div className="flex items-center gap-2">
<Link href={`/apps/${app.slug}`} className="truncate text-sm font-semibold transition hover:text-cyan-300 md:text-base">
{app.name}
</Link>
<span className="rounded-md border border-cyan-300/35 bg-cyan-400/10 px-1.5 py-0.5 text-[10px] text-cyan-100">{app.version}</span>
</div>
<p className="mt-1 truncate text-xs text-slate-400 md:text-sm">{app.subtitle}</p>
<div className="mt-2 flex flex-wrap gap-1.5">
{app.tags.slice(0, 3).map((tag) => (
<span key={tag} className="pill py-1">{tag}</span>
))}
</div>
</div>
</div>
<div className="grid grid-cols-3 gap-2 text-xs text-slate-400 md:w-[320px] md:text-sm">
<div className="rounded-lg border border-white/10 px-2 py-2 text-center">
<p className="text-[10px] uppercase tracking-wide text-slate-500"></p>
<p className="mt-1 text-slate-200">{app.updatedAt}</p>
</div>
<div className="rounded-lg border border-white/10 px-2 py-2 text-center">
<p className="text-[10px] uppercase tracking-wide text-slate-500"></p>
<p className="mt-1 text-slate-200">{app.size}</p>
</div>
<div className="rounded-lg border border-white/10 px-2 py-2 text-center">
<p className="text-[10px] uppercase tracking-wide text-slate-500"></p>
<p className="mt-1 text-slate-200">{app.downloads}</p>
</div>
</div>
</div>
))}
</div>
) : (
<div className="panel text-center">
<p className="text-sm text-slate-300"></p>
<button onClick={() => setQuery("")} className="mt-3 cta-sub"></button>
</div>
)}
{count < filtered.length && (
<div className="pt-1">
<button onClick={() => setCount((c) => c + 6)} className="mx-auto block cta-sub">
</button>
</div>
)}
</section>
);
}