Add js web stuff to landing page + documentation
This commit is contained in:
176
go/cmd/kjol-web/frontend/src/layout/Shell.tsx
Normal file
176
go/cmd/kjol-web/frontend/src/layout/Shell.tsx
Normal file
@@ -0,0 +1,176 @@
|
||||
// The Kjol 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 } from "@solidjs/router";
|
||||
import { Icon } from "@ui/Icons";
|
||||
import { Menu, MenuTrigger, MenuContent, MenuLink, MenuSection } from "@ui/Menu";
|
||||
import { ThemeToggle, initTheme } from "@ui/Theme";
|
||||
import { LAYERS } from "../layers.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: "/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" },
|
||||
];
|
||||
|
||||
// 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.
|
||||
//
|
||||
// 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() {
|
||||
return (
|
||||
<Menu>
|
||||
<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
|
||||
<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>
|
||||
</MenuContent>
|
||||
</Menu>
|
||||
);
|
||||
}
|
||||
|
||||
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
|
||||
// back where you started.
|
||||
return (
|
||||
<a href="/" class="flex items-center gap-2.5 no-underline">
|
||||
<span class="inline-flex h-8 w-8 items-center justify-center rounded-default bg-fill-neutral text-on-fill-neutral">
|
||||
<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-sm text-ink-faint">Solid + Go toolchain</span>
|
||||
</span>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
function Sidebar() {
|
||||
const location = useLocation();
|
||||
// The router's pathname is absolute (/js/kit); NAV paths are base-relative (/kit).
|
||||
const active = (path: string) => location.pathname === "/js" + (path === "/" ? "" : path);
|
||||
|
||||
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>
|
||||
<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"
|
||||
}
|
||||
>
|
||||
<Icon
|
||||
icon={item.icon}
|
||||
size={14}
|
||||
class={active(item.path) ? "text-primary" : "text-ink-faint"}
|
||||
/>
|
||||
{item.label}
|
||||
</A>
|
||||
</li>
|
||||
)}
|
||||
</For>
|
||||
</ul>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
|
||||
export function Shell(props: { children?: any }) {
|
||||
// Once, at the root. The boot script in the document head has ALREADY put the right
|
||||
// class on <html> — 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 (
|
||||
<div class="min-h-screen bg-surface">
|
||||
{/* bg-surface/90, not bg-white/90: the translucent sticky bar has to be
|
||||
translucent over whatever the surface currently IS. */}
|
||||
<nav class="sticky top-0 z-20 border-b border-line bg-surface/90 backdrop-blur">
|
||||
<div class="mx-auto flex max-w-[110rem] items-center gap-3 px-6 py-3">
|
||||
<Wordmark />
|
||||
<span class="rounded-full border border-line px-2 py-0.5 text-[11px] font-semibold uppercase tracking-wider text-ink-faint">
|
||||
Docs
|
||||
</span>
|
||||
<div class="ml-auto flex items-center gap-2">
|
||||
<LayersMenu />
|
||||
<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"
|
||||
>
|
||||
Home
|
||||
</a>
|
||||
<ThemeToggle small />
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="mx-auto flex max-w-[110rem] gap-8 px-6">
|
||||
<Sidebar />
|
||||
<main class="min-w-0 flex-1 py-10">{props.children}</main>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user