Map records to generic browser rows.
CONTENT-WP-0002-T03 renders manifest columns through a pure row mapper and replaceable cell renderers. Assistant: grok Assistant-Session: 01a1346c-fdff-7741-b1af-b7283661554b
This commit is contained in:
parent
e16aa03393
commit
d92eb25f92
12 changed files with 237 additions and 53 deletions
|
|
@ -25,7 +25,7 @@ export function DataspaceHome({ id }: { id: string }) {
|
|||
))}
|
||||
</p>
|
||||
) : null}
|
||||
<RecordBrowser rows={dataspace.rows} />
|
||||
<RecordBrowser rows={dataspace.rows} columns={dataspace.columns} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ describe("kind and detail views", () => {
|
|||
expect(html).toContain(">Concepts<");
|
||||
expect(html).toContain("Coupling");
|
||||
expect(html).toContain('href="/software-architecture/concept/coupling/"');
|
||||
expect(html).toContain("dependency");
|
||||
expect(html).toContain(">Tags<");
|
||||
});
|
||||
|
||||
it("renders the entity title, an unknown claim, and a neighbour", () => {
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ export function KindIndex({ kind }: { kind: KindView }) {
|
|||
return (
|
||||
<>
|
||||
<PageHeader kicker={kind.dataspaceTitle} title={kind.plural} lede={lede} />
|
||||
<RecordBrowser rows={kind.rows} />
|
||||
<RecordBrowser rows={kind.rows} columns={kind.columns} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,36 +1,36 @@
|
|||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { renderCell, type CellRenderer } from "@/components/cell-renderers";
|
||||
import { ResultsTable } from "@/components/ResultsTable";
|
||||
import { cellSortValue, type BrowserRow, type ColumnDef } from "@/lib/browser-row";
|
||||
import { nextSort, sortRows } from "@/lib/sort-rows";
|
||||
import type { RecordRow, SortState } from "@/lib/types";
|
||||
import type { SortState } from "@/lib/types";
|
||||
|
||||
const columns = [
|
||||
{
|
||||
key: "title",
|
||||
label: "Title",
|
||||
export function RecordBrowser({
|
||||
rows,
|
||||
columns,
|
||||
heading = "Records",
|
||||
renderers,
|
||||
}: {
|
||||
rows: BrowserRow[];
|
||||
columns: ColumnDef[];
|
||||
heading?: string;
|
||||
renderers?: Record<string, CellRenderer>;
|
||||
}) {
|
||||
const [sort, setSort] = useState<SortState | undefined>({ key: columns[0]?.key ?? "name", dir: 1 });
|
||||
const ordered = sortRows(rows, sort, cellSortValue);
|
||||
const tableColumns = columns.map((column) => ({
|
||||
key: column.key,
|
||||
label: column.label,
|
||||
sortable: true,
|
||||
render: (row: RecordRow) => row.href ? <a className="underline" href={row.href}>{row.title}</a> : row.title,
|
||||
},
|
||||
{
|
||||
key: "kindLabel",
|
||||
label: "Kind",
|
||||
sortable: true,
|
||||
render: (row: RecordRow) => row.kindHref
|
||||
? <a className="chip" href={row.kindHref}>{row.kindLabel}</a>
|
||||
: <span className="chip">{row.kindLabel}</span>,
|
||||
},
|
||||
{ key: "tldr", label: "Summary", sortable: true, render: (row: RecordRow) => row.tldr },
|
||||
];
|
||||
|
||||
export function RecordBrowser({ rows, heading = "Records" }: { rows: RecordRow[]; heading?: string }) {
|
||||
const [sort, setSort] = useState<SortState | undefined>({ key: "title", dir: 1 });
|
||||
const ordered = sortRows(rows, sort, (row, key) => String(row[key as keyof RecordRow] ?? ""));
|
||||
render: (row: BrowserRow) => renderCell(column.key, row, renderers),
|
||||
}));
|
||||
return (
|
||||
<section id="records" aria-labelledby="records-heading" className="mx-auto max-w-7xl px-4 py-14 sm:px-6">
|
||||
<h2 id="records-heading" className="mb-6 text-2xl">{heading}</h2>
|
||||
<ResultsTable
|
||||
columns={columns}
|
||||
columns={tableColumns}
|
||||
rows={ordered}
|
||||
rowKey={(row) => row.id}
|
||||
sort={sort}
|
||||
|
|
|
|||
39
ui/components/cell-renderers.tsx
Normal file
39
ui/components/cell-renderers.tsx
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
import type { ReactNode } from "react";
|
||||
import type { BrowserRow, CellValue } from "@/lib/browser-row";
|
||||
|
||||
/**
|
||||
* Generic cell renderers. Callers replace any key or cell kind.
|
||||
* OnCo's table cells for molecules, flags, and domain icons are not included.
|
||||
*/
|
||||
export type CellRenderer = (cell: CellValue, row: BrowserRow) => ReactNode;
|
||||
|
||||
export function renderGenericCell(cell: CellValue): ReactNode {
|
||||
if (cell.kind === "text") return cell.text;
|
||||
if (cell.kind === "link") {
|
||||
return <a className="underline" href={cell.href}>{cell.label}</a>;
|
||||
}
|
||||
return (
|
||||
<span className="inline-flex flex-wrap gap-1">
|
||||
{cell.values.map((value) => (
|
||||
<span key={value} className="chip">{value}</span>
|
||||
))}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
export const genericCellRenderers: Record<string, CellRenderer> = {
|
||||
text: (cell) => renderGenericCell(cell),
|
||||
link: (cell) => renderGenericCell(cell),
|
||||
chips: (cell) => renderGenericCell(cell),
|
||||
};
|
||||
|
||||
export function renderCell(
|
||||
key: string,
|
||||
row: BrowserRow,
|
||||
registry: Record<string, CellRenderer> = genericCellRenderers,
|
||||
): ReactNode {
|
||||
const cell = row.cols[key];
|
||||
if (!cell) return null;
|
||||
const render = registry[key] ?? registry[cell.kind] ?? renderGenericCell;
|
||||
return render(cell, row);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue