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:
@@ -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 }))
|
||||
},
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user