Update kjol website with C documentation

This commit is contained in:
2026-07-14 13:05:12 -04:00
parent 02a6dc6c48
commit 7d7b7354df
66 changed files with 23884 additions and 2551 deletions

View File

@@ -1,4 +1,4 @@
// The Kjol JS Web shell: top bar (wordmark + Layers menu), sidebar, content.
// 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
@@ -7,11 +7,12 @@
// and the page should not betray that.
import { For, Show } from "solid-js";
import { A, useLocation } from "@solidjs/router";
import { A, useLocation, useNavigate } from "@solidjs/router";
import { Icon } from "@ui/Icons";
import { Menu, MenuTrigger, MenuContent, MenuLink, MenuSection } from "@ui/Menu";
import { Menu, MenuTrigger, MenuContent } from "@ui/Menu";
import { ThemeToggle, initTheme } from "@ui/Theme";
import { LAYERS } from "../layers.ts";
import { LANGUAGES, COMPOSITIONS, currentLayer, Layer } from "../layers.ts";
import { COMPONENT_GROUPS } from "../componentGroups.ts";
interface NavItem {
path: string;
@@ -22,68 +23,109 @@ interface NavItem {
// The section's own pages. Paths are relative to the router base (/js).
const NAV: NavItem[] = [
{ path: "/", label: "Overview", icon: "circle-info" },
{ path: "/kit", label: "Components", icon: "table-columns" },
{ path: "/forms", label: "Forms", icon: "pen-to-square" },
{ path: "/table", label: "AutoTable", icon: "table" },
{ path: "/theming", label: "Theming", icon: "palette" },
{ path: "/components", label: "Components", icon: "table-columns" },
];
// The Layers menu — the site's primary navigation. kjol is a stack of layers, and
// this is how you get from any one of them to any other. It is rendered from the
// LAYERS array so adding a layer is one object, not a nav edit in two front-ends.
// jumpTo scrolls a section into view, routing there first if we are somewhere else.
//
// Layers that are not `live` still appear. A menu that silently omits half the
// library teaches the reader that the library is half the size it is; showing them
// greyed, with the reason, is the more honest shape.
function LayersMenu() {
// A plain <a href="#forms"> would work if the reader were already on the components
// page, and would do nothing useful from anywhere else. The router's <A> 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 (
<Menu>
// 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.
<Menu placement="bottom-end">
<MenuTrigger>
<span class="inline-flex items-center gap-1.5 rounded-default px-3 py-1.5 text-sm font-medium text-ink-soft hover:bg-surface-raised hover:text-ink">
Layers
{props.label}
<Icon icon="chevron-down" size={11} class="text-ink-faint" />
</span>
</MenuTrigger>
<MenuContent class="w-96">
<MenuSection>
<p class="px-3 pb-1 pt-2 text-[11px] font-semibold uppercase tracking-widest text-ink-faint">
The layers of kjol
</p>
<For each={LAYERS}>
{(layer) => (
<Show
when={layer.live}
fallback={
<div class="flex cursor-default flex-col gap-0.5 px-3 py-2 opacity-55">
<span class="flex items-center gap-2 text-sm font-medium text-ink-muted">
<Icon icon={layer.icon} size={14} class="shrink-0 text-ink-faint" />
{layer.name}
<span class="rounded-full bg-surface-raised px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wider text-ink-muted">
reference
</span>
</span>
<span class="pl-6 text-xs text-ink-muted">{layer.tagline}</span>
</div>
}
>
{/* MenuLink is a real <a href> (not a router link), which is what a
cross-layer jump has to be: the other layers are served by a
different binary. */}
<MenuLink href={layer.href} icon={layer.icon}>
<span class="flex flex-col gap-0.5">
<span class="text-sm font-medium text-ink">{layer.name}</span>
<span class="text-xs text-ink-muted">{layer.tagline}</span>
</span>
</MenuLink>
</Show>
)}
</For>
</MenuSection>
<For each={props.rows}>{(layer) => <LayerItem layer={layer} current={current()} />}</For>
</MenuContent>
</Menu>
);
}
// 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 (
<Show
when={props.layer.live}
fallback={
<div class="flex cursor-default flex-col gap-0.5 px-3 py-2 opacity-55">
<span class="flex items-center gap-2 text-sm font-medium text-ink-muted">
<Icon icon={props.layer.icon} size={14} class="shrink-0 text-ink-faint" />
{props.layer.name}
<span class="rounded-full bg-surface-raised px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wider text-ink-muted">
reference
</span>
</span>
<span class="pl-6 text-xs text-ink-muted">{props.layer.tagline}</span>
</div>
}
>
{/* A plain <a href>, not MenuLink and not the router's <A>. Both of the
alternatives are wrong here: MenuLink lays its icon out BESIDE the whole
two-line block (so the tagline never lines up under the name, which is what
the Go menu does), and the router would try to handle the jump itself — but
the other side is served by a different binary, so it has to be a real
navigation. */}
<a
href={props.layer.href}
class={
"flex flex-col gap-0.5 px-3 py-2 no-underline hover:bg-surface-raised " +
(props.current?.href === props.layer.href ? "bg-primary-subtle" : "")
}
>
<span class="flex items-center gap-2 text-sm font-medium text-ink">
<Icon icon={props.layer.icon} size={14} class="shrink-0 text-accent" />
{props.layer.name}
</span>
<span class="pl-6 text-xs text-ink-muted">{props.layer.tagline}</span>
</a>
</Show>
);
}
function Wordmark() {
// A plain <a href>, not a router <A>: "/" is the front page, which belongs to the
// Go/WASM binary. Routing to it inside this SPA would resolve to /js and land you
@@ -94,7 +136,7 @@ function Wordmark() {
<Icon icon="sailboat" size={17} />
</span>
<span class="flex items-baseline gap-1.5">
<span class="text-lg font-semibold tracking-tight text-ink">Kjol JS Web</span>
<span class="text-lg font-semibold tracking-tight text-ink">Kjøl JS Web</span>
<span class="text-sm text-ink-faint">Solid + Go toolchain</span>
</span>
</a>
@@ -103,29 +145,32 @@ function Wordmark() {
function Sidebar() {
const location = useLocation();
// The router's pathname is absolute (/js/kit); NAV paths are base-relative (/kit).
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.
const linkCls = (on: boolean) =>
on
? "flex items-center gap-2 rounded-default bg-primary-subtle px-2 py-1.5 text-sm font-medium text-accent no-underline"
: "flex items-center gap-2 rounded-default px-2 py-1.5 text-sm text-ink-soft no-underline hover:bg-surface-raised hover:text-ink";
return (
<aside class="sticky top-[3.75rem] hidden h-[calc(100vh-3.75rem)] w-56 shrink-0 overflow-y-auto py-10 lg:block">
<p class="px-2 text-[11px] font-semibold uppercase tracking-widest text-ink-faint">Kjol JS Web</p>
<p class="px-2 text-[11px] font-semibold uppercase tracking-widest text-ink-faint">Kjøl JS Web</p>
<ul class="mt-2 space-y-0.5">
<For each={NAV}>
{(item) => (
<li>
<A
href={item.path}
end={item.path === "/"}
class={
active(item.path)
? "flex items-center gap-2 rounded-default bg-surface-raised px-2 py-1.5 text-sm font-medium text-primary no-underline"
: "flex items-center gap-2 rounded-default px-2 py-1.5 text-sm text-ink-soft no-underline hover:bg-surface-muted hover:text-ink"
}
>
<A href={item.path} end={item.path === "/"} class={linkCls(active(item.path))}>
<Icon
icon={item.icon}
size={14}
class={active(item.path) ? "text-primary" : "text-ink-faint"}
class={active(item.path) ? "text-accent" : "text-ink-faint"}
/>
{item.label}
</A>
@@ -133,6 +178,36 @@ function Sidebar() {
)}
</For>
</ul>
{/* The component groups are not pages — they are anchors into the one components
page, and clicking one scrolls you there.
They are not highlighted by which section you have scrolled to. Finding that
out means measuring all fifteen of them on every scroll frame, and the only
way to act on the answer is a state write, which re-renders. Sixty times a
second, to move a highlight. The highlight is not worth the page. */}
<p class="mt-8 px-2 text-[11px] font-semibold uppercase tracking-widest text-ink-faint">
Components
</p>
<ul class="mt-2 space-y-0.5">
<For each={COMPONENT_GROUPS}>
{(g) => (
<li>
<a
href={"/js/components#" + g.id}
class={linkCls(false)}
onclick={(e) => {
e.preventDefault();
jumpTo(navigate, onComponents(), g.id);
}}
>
<Icon icon={g.icon} size={14} class="text-ink-faint" />
{g.label}
</a>
</li>
)}
</For>
</ul>
</aside>
);
}
@@ -155,7 +230,8 @@ export function Shell(props: { children?: any }) {
Docs
</span>
<div class="ml-auto flex items-center gap-2">
<LayersMenu />
<Dropdown label="Layers" rows={LANGUAGES} />
<Dropdown label="Compositions" rows={COMPOSITIONS} />
<a
href="/"
class="rounded-default px-3 py-1.5 text-sm font-medium text-ink-soft no-underline hover:bg-surface-raised hover:text-ink"