feat: add AirShelf core implementation

This commit is contained in:
zyc
2026-06-05 10:21:40 +08:00
parent 2ba1058329
commit cfdcd84a30
252 changed files with 70828 additions and 0 deletions
+33
View File
@@ -0,0 +1,33 @@
import { useState } from "react";
import type { FormEvent } from "react";
import { Search, Upload } from "lucide-react";
import type { Asset } from "../types";
import { Drawer } from "../components/overlays";
export function LibraryPage({ assets, onUpload }: { assets: Asset[]; onUpload: (formData: FormData) => Promise<unknown> | void }) {
const [drawer, setDrawer] = useState(false);
const [file, setFile] = useState<File | null>(null);
const [name, setName] = useState("");
async function submit(event: FormEvent) {
event.preventDefault();
if (!file) return;
const formData = new FormData();
formData.append("file", file);
formData.append("name", name || file.name);
formData.append("asset_type", "image");
formData.append("category", "upload");
await onUpload(formData);
setDrawer(false);
}
return (
<>
<div className="page-head"><div><h1></h1><div className="sub"><span className="mono">// 跨项目复用 · {assets.length} assets</span></div></div><div className="actions"><button className="btn" type="button" onClick={() => setDrawer(true)}><Upload size={13} />上传资产</button></div></div>
<div className="tabs"><button className="tab active"> <span className="count">{assets.length}</span></button><button className="tab"></button><button className="tab"></button><button className="tab"></button><button className="tab"></button></div>
<div className="toolbar"><div className="search-inline"><Search size={14} /><input className="input" placeholder="搜索资产" /></div><button className="chip active"></button><button className="chip">使</button></div>
<div className="asset-grid">{assets.map((asset) => <article className={`asset-card ${asset.asset_type}`} key={asset.id}><div className="placeholder asset-thumb"><span className="ph-frame">{asset.asset_type}</span></div><div className="asset-body"><div className="asset-name">{asset.name}</div><div className="asset-meta">{asset.category} · {asset.source}</div></div></article>)}</div>
<Drawer title="上传资产" open={drawer} close={() => setDrawer(false)}><form onSubmit={submit}><div className="field"><label className="field-label"></label><input className="input file-input" type="file" onChange={(event) => setFile(event.target.files?.[0] || null)} /></div><div className="field"><label className="field-label"></label><input className="input" value={name} onChange={(event) => setName(event.target.value)} /></div><div className="drawer-actions"><button className="btn btn-ghost" type="button" onClick={() => setDrawer(false)}></button><button className="btn btn-primary" type="submit" disabled={!file}></button></div></form></Drawer>
</>
);
}