// The Kjøl JS Web shell: top bar (wordmark + Layers menu), sidebar, content. // // It is deliberately a near-copy of the Go/WASM section's AppLayout. Two front-ends, // one site: if the chrome drifted, crossing from /wasm to /js would feel like leaving // for somebody else's website. The components underneath are completely different — // these are Solid components from the kit, those are Go functions returning a VNode — // and the page should not betray that. import { For, Show } from "solid-js"; import { A, useLocation, useNavigate } from "@solidjs/router"; import { Icon } from "@ui/Icons"; import { Menu, MenuTrigger, MenuContent } from "@ui/Menu"; import { ThemeToggle, initTheme } from "@ui/Theme"; import { LANGUAGES, COMPOSITIONS, currentLayer, Layer } from "../layers.ts"; import { COMPONENT_GROUPS } from "../componentGroups.ts"; interface NavItem { path: string; label: string; icon: string; } // The section's own pages. Paths are relative to the router base (/js). const NAV: NavItem[] = [ { path: "/", label: "Overview", icon: "circle-info" }, { path: "/components", label: "Components", icon: "table-columns" }, ]; // jumpTo scrolls a section into view, routing there first if we are somewhere else. // // A plain would work if the reader were already on the components // page, and would do nothing useful from anywhere else. The router's is no good // either — it would try to navigate to a route called "#forms". // // The queueMicrotask is not superstition: after navigate() the target section does not // exist yet, because the page it lives on has not rendered. Scrolling on the next tick // is the earliest moment the element is actually there to scroll to. function jumpTo(navigate: (to: string) => void, onComponentsPage: boolean, id: string) { const scroll = () => document.getElementById(id)?.scrollIntoView({ behavior: "smooth", block: "start" }); if (onComponentsPage) { scroll(); return; } navigate("/components"); queueMicrotask(scroll); } // The site's primary navigation, as TWO dropdowns: Layers (the languages) and // Compositions (the frameworks assembled out of them — see layers.ts). // // Two menus, not one with two headings inside it. They answer different questions — // "what is this written in" and "what can I read" — and a reader who wants the second // should not have to scroll past the first to find it. The kit's single-open manager // means opening one closes the other, so they behave like one control with two halves. // // Anything not `live` still appears, greyed, with the reason. A menu that silently omits // half the library teaches the reader that the library is half the size it is. // // Same shape as the Go side (app/layers.go: layersMenu / compositionsMenu). function Dropdown(props: { label: string; rows: Layer[] }) { const location = useLocation(); const current = () => currentLayer(location.pathname); return ( // bottom-end, because the triggers sit at the right-hand end of the bar and a 24rem // panel hanging off the left edge of one would run past the window. {props.label} {(layer) => } ); } // LayerItem is one row of the menu. // // The markup is deliberately the same shape and the same classes as app/layers.go's // layerItem. Two front-ends, one menu: if they drifted, this is where it would show, // because it is the one component a reader sees on both sides within seconds of each // other. function LayerItem(props: { layer: Layer; current?: Layer }) { return ( {props.layer.name} reference {props.layer.tagline} } > {/* A plain , not the router's : the other side is served by a different binary, so crossing to it has to be a real navigation, not a client-side route the router would try to handle itself. */} {props.layer.name} {props.layer.tagline} ); } function Wordmark() { // A plain , not a router : "/" is the front page, which belongs to the // Go/WASM binary. Routing to it inside this SPA would resolve to /js and land you // back where you started. return ( {/* text-white, not a theme token: the flag tile is the same in both themes, so the boat on top of it has to be too. The flag carries a dark scrim (.flag-no) so the plain white boat reads without a shadow of its own. */} Kjøl JS Web Solid + Go toolchain ); } function Sidebar() { const location = useLocation(); const navigate = useNavigate(); // The router's pathname is absolute (/js/components); NAV paths are base-relative. const active = (path: string) => location.pathname === "/js" + (path === "/" ? "" : path); const onComponents = () => location.pathname === "/js/components"; // The same active treatment the Go sidebar uses (app/pages.go: sidebarLink) — a // tinted panel and accent text, not a grey fill. Now that the Solid theme carries the // primary-subtle / accent tokens, the two sidebars are the same sidebar. // // No icons, also matching the Go sidebar: the sidebar is a list of words, and a glyph on // every row is noise to read past. So `block`, not `flex items-center gap-2`. const linkCls = (on: boolean) => on ? "block rounded-default bg-primary-subtle px-2 py-1.5 text-sm font-medium text-accent no-underline" : "block rounded-default px-2 py-1.5 text-sm text-ink-soft no-underline hover:bg-surface-raised hover:text-ink"; return ( ); } export function Shell(props: { children?: any }) { // Once, at the root. The boot script in the document head has ALREADY put the right // class on — this only syncs the toggle's signals with it and starts // following the OS while the mode is "system". Calling it late is harmless; not // calling it just leaves the button showing the wrong icon. initTheme(); return (
{/* bg-surface/90, not bg-white/90: the translucent sticky bar has to be translucent over whatever the surface currently IS. */}
{props.children}
); }