Spaces:
Runtime error
Runtime error
Download frontend/src/components/DocPicker.tsx from firebird-technologies/NoteStack-Test: direct link, hf CLI and curl.
- Browser
- Download file 2.9 kB
-
https://huggingface.co/spaces/firebird-technologies/NoteStack-Test/resolve/52ec279e6108c8a40d96dbfbb4d5573a02472df3/frontend/src/components/DocPicker.tsx
- Command line
-
hf download hf://spaces/firebird-technologies/NoteStack-Test@52ec279e6108c8a40d96dbfbb4d5573a02472df3/frontend/src/components/DocPicker.tsx
-
curl -L -o DocPicker.tsx https://huggingface.co/spaces/firebird-technologies/NoteStack-Test/resolve/52ec279e6108c8a40d96dbfbb4d5573a02472df3/frontend/src/components/DocPicker.tsx
2.9 kB
| import { useEffect, useMemo, useState } from "react"; | |
| import { docsApi } from "../api/endpoints"; | |
| import type { Doc } from "../api/types"; | |
| import { formatDate, Loading } from "./ui"; | |
| /** Searchable checklist of posts. `single` turns it into a radio list. */ | |
| export function DocPicker({ | |
| selected, | |
| onChange, | |
| single = false, | |
| exclude = [], | |
| max, | |
| }: { | |
| selected: string[]; | |
| onChange: (ids: string[]) => void; | |
| single?: boolean; | |
| exclude?: string[]; | |
| max?: number; | |
| }) { | |
| const [docs, setDocs] = useState<Doc[] | null>(null); | |
| const [q, setQ] = useState(""); | |
| useEffect(() => { | |
| docsApi.list({ limit: 5000 }).then((p) => setDocs(p.items)); | |
| }, []); | |
| const visible = useMemo(() => { | |
| const skip = new Set(exclude); | |
| const needle = q.trim().toLowerCase(); | |
| return (docs ?? []).filter( | |
| (d) => !skip.has(d.id) && (!needle || d.title.toLowerCase().includes(needle) || (d.source_title ?? "").toLowerCase().includes(needle)), | |
| ); | |
| }, [docs, q, exclude]); | |
| if (!docs) return <Loading label="Loading posts" />; | |
| if (docs.length === 0) return <p className="muted">No posts yet. Connect a source first.</p>; | |
| const toggle = (id: string) => { | |
| if (single) return onChange([id]); | |
| if (selected.includes(id)) return onChange(selected.filter((x) => x !== id)); | |
| if (max && selected.length >= max) return; | |
| onChange([...selected, id]); | |
| }; | |
| return ( | |
| <div className="picker"> | |
| <div className="picker-bar"> | |
| <input className="input input-sm" placeholder="Search posts" value={q} onChange={(e) => setQ(e.target.value)} aria-label="Search posts" /> | |
| {!single && ( | |
| <div className="picker-actions mono muted"> | |
| <span>{selected.length} selected{max ? ` of ${max}` : ""}</span> | |
| {!max && ( | |
| <button type="button" className="link-btn" onClick={() => onChange(Array.from(new Set([...selected, ...visible.map((d) => d.id)])))}> | |
| Select all | |
| </button> | |
| )} | |
| {selected.length > 0 && ( | |
| <button type="button" className="link-btn" onClick={() => onChange([])}> | |
| Clear | |
| </button> | |
| )} | |
| </div> | |
| )} | |
| </div> | |
| <ul className="picker-list"> | |
| {visible.map((d) => ( | |
| <li key={d.id}> | |
| <label className={`picker-row${selected.includes(d.id) ? " on" : ""}`}> | |
| <input type={single ? "radio" : "checkbox"} name="doc-picker" checked={selected.includes(d.id)} onChange={() => toggle(d.id)} /> | |
| <span className="picker-title">{d.title}</span> | |
| <span className="mono muted picker-meta"> | |
| {formatDate(d.published_at)} · {d.words} words | |
| </span> | |
| </label> | |
| </li> | |
| ))} | |
| {visible.length === 0 && <li className="muted">No posts match.</li>} | |
| </ul> | |
| </div> | |
| ); | |
| } | |