1import ChevronUp from "lucide-solid/icons/chevron-up";
2import { children, createSignal, type JSX, Show } from "solid-js";
3
4/**
5 * A page whose head and summary stay put while the body scrolls under them; tables in the body keep their header
6 * row pinned. The summary folds away behind a handle, remembered per `id`. A body child with class `fill` takes the
7 * leftover height and scrolls on its own.
8 */
9export function ListPage(props: {
10 id: string;
11 class?: string;
12 head: JSX.Element;
13 summary?: JSX.Element;
14 /** Body children with class `fill` or `edge`, and tables, run edge to edge; the rest keep the page gutter. */
15 flush?: boolean;
16 children: JSX.Element;
17}) {
18 const key = `list-page.${props.id}.collapsed`;
19 const [collapsed, setCollapsed] = createSignal(localStorage.getItem(key) === "true");
20 const summary = children(() => props.summary);
21 return (
22 <div class={`page list-page ${props.class ?? ""}`}>
23 {props.head}
24 <Show when={summary.toArray().length}>
25 <div class="summary" classList={{ open: !collapsed() }} inert={collapsed()}>
26 <div>{summary()}</div>
27 </div>
28 <button class="summary-toggle" aria-expanded={!collapsed()} aria-label={collapsed() ? "Show summary" : "Hide summary"}
29 data-tip={collapsed() ? "show summary" : "hide summary"} onClick={() => {
30 setCollapsed(!collapsed());
31 localStorage.setItem(key, String(collapsed()));
32 }}>
33 <ChevronUp size={14} class={collapsed() ? "flipped" : ""} />
34 </button>
35 </Show>
36 <div class="list-body" classList={{ flush: props.flush }}>{props.children}</div>
37 </div>
38 );
39}