// 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.
);
}
// 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 (