refactor(website): split the hero terminal and the page components

HeroTerminal was a single 367-line component doing three separate jobs:
simulating the transfer queue, holding the per-search result sets, and
rendering. It is now useTransfers, useResults and useHeroDemo behind a
presentational component. The dead shellRef went with it.

The home, docs and install pages each returned one long JSX block; every
section is now a named component, so the page body reads as an outline.

The prerendered HTML is byte-identical for all five pages, and the extracted
transfer reducer matches the original over 200 ticks.
This commit is contained in:
Michel de Graaf
2026-07-26 15:40:58 +02:00
parent 093f32b360
commit 2e9cd6b860
7 changed files with 1128 additions and 1028 deletions
+77
View File
@@ -0,0 +1,77 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { RESULTS, type ResultRow } from './data'
type Results = Readonly<Record<string, readonly ResultRow[]>>
/** Result sets per search, which row is selected, and what is marked or already queued. */
export const useResults = (queue: (rows: readonly ResultRow[]) => void) => {
const [results, setResults] = useState<Results>(RESULTS)
const [activeId, setActiveId] = useState('s1')
const [selected, setSelected] = useState(0)
const resultsRef = useRef(results)
useEffect(() => {
resultsRef.current = results
}, [results])
const rows = useMemo(() => results[activeId] ?? [], [results, activeId])
const patch = useCallback(
(searchId: string, edit: (row: ResultRow, index: number) => ResultRow) => {
setResults((prev) => ({ ...prev, [searchId]: (prev[searchId] ?? []).map(edit) }))
},
[],
)
const queueFrom = useCallback(
(searchId: string, indexes: readonly number[]) => {
const current = resultsRef.current[searchId] ?? []
const picked = indexes
.map((i) => current[i])
.filter((row): row is ResultRow => row !== undefined && row.queued !== true)
if (picked.length === 0) return
const wanted = new Set(picked.map((row) => row.name))
patch(searchId, (row) =>
wanted.has(row.name) ? { ...row, queued: true, marked: false } : row,
)
queue(picked)
},
[patch, queue],
)
const mark = useCallback(
(row: number) => {
patch('s1', (r, i) => (i === row ? { ...r, marked: true } : r))
},
[patch],
)
const open = useCallback((id: string, next?: readonly ResultRow[]) => {
if (next) setResults((prev) => ({ ...prev, [id]: next }))
setActiveId(id)
setSelected(0)
}, [])
return {
rows,
activeId,
selected,
setSelected,
open,
mark,
queueFrom,
queueIndexes: (indexes: readonly number[]) => {
queueFrom(activeId, indexes)
},
toggleMark: (index: number) => {
patch(activeId, (row, i) =>
i === index && row.queued !== true ? { ...row, marked: row.marked !== true } : row,
)
},
markAll: (on: boolean) => {
patch(activeId, (row) => (row.queued === true ? row : { ...row, marked: on }))
},
}
}