Add js web stuff to landing page + documentation
This commit is contained in:
103
go/jsruntime/uikit/Sidebar.tsx
Normal file
103
go/jsruntime/uikit/Sidebar.tsx
Normal file
@@ -0,0 +1,103 @@
|
||||
import { For, createSignal, Show, JSXElement } from "solid-js";
|
||||
|
||||
const NAV_ROOT = "bg-surface rounded-default shadow-sm border border-line py-2";
|
||||
const NAV_LIST = "list-none m-0 p-0";
|
||||
const NAV_BTN = "w-full text-left py-2 pr-3 pl-4 text-sm cursor-pointer bg-transparent text-ink-soft border-0 hover:text-ink hover:bg-surface-muted";
|
||||
const NAV_SUBBTN = "w-full text-left py-1.5 pr-3 pl-8 text-xs cursor-pointer bg-transparent text-ink-muted border-0 hover:text-ink hover:bg-surface-muted";
|
||||
const NAV_ICON = "mr-2";
|
||||
|
||||
const LAYOUT_ROOT = "grid grid-cols-1 gap-8 min-h-screen items-start lg:grid-cols-12";
|
||||
const LAYOUT_SIDEBAR = "hidden lg:block lg:col-span-2 lg:self-start lg:h-full";
|
||||
const LAYOUT_STICKY = "sticky top-20 max-h-[calc(100vh_-_7rem)] overflow-y-auto";
|
||||
const LAYOUT_STICKY_FULL = "sticky top-0 h-screen overflow-y-auto";
|
||||
// lg:pr-8 mirrors the grid's gap-8 (the content's left spacing from the sidebar)
|
||||
// so the content has matching breathing room on the right instead of hugging the
|
||||
// viewport edge. Scoped to lg, where the sidebar/gap exists.
|
||||
const LAYOUT_MAIN = "col-span-1 min-w-0 lg:col-span-10 lg:pr-8";
|
||||
|
||||
interface SidebarNavItem {
|
||||
id: string;
|
||||
label: string;
|
||||
icon?: JSXElement;
|
||||
// Optional second-level items that jump to sub-sections within this item.
|
||||
children?: SidebarNavItem[];
|
||||
}
|
||||
|
||||
interface SidebarNavProps {
|
||||
items: SidebarNavItem[];
|
||||
onItemClick?: (id: string) => void;
|
||||
class?: string;
|
||||
}
|
||||
|
||||
export function SidebarNav(props: SidebarNavProps) {
|
||||
const handleClick = (id: string) => {
|
||||
if (props.onItemClick) {
|
||||
props.onItemClick(id);
|
||||
}
|
||||
const element = document.getElementById(id);
|
||||
if (element) {
|
||||
element.scrollIntoView({ behavior: "smooth", block: "start" });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<nav class={NAV_ROOT + (props.class ? " " + props.class : "")}>
|
||||
<ul class={NAV_LIST}>
|
||||
<For each={props.items}>{(item: SidebarNavItem) => (
|
||||
<li>
|
||||
<button onclick={() => handleClick(item.id)} class={NAV_BTN}>
|
||||
{item.icon ? <span class={NAV_ICON}>{item.icon}</span> : null}
|
||||
{item.label}
|
||||
</button>
|
||||
<Show when={item.children && item.children.length}>
|
||||
<ul class={NAV_LIST}>
|
||||
<For each={item.children}>{(sub: SidebarNavItem) => (
|
||||
<li>
|
||||
<button onclick={() => handleClick(sub.id)} class={NAV_SUBBTN}>
|
||||
{sub.icon ? <span class={NAV_ICON}>{sub.icon}</span> : null}
|
||||
{sub.label}
|
||||
</button>
|
||||
</li>
|
||||
)}</For>
|
||||
</ul>
|
||||
</Show>
|
||||
</li>
|
||||
)}</For>
|
||||
</ul>
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
|
||||
interface SidebarLayoutProps {
|
||||
sidebar: JSXElement;
|
||||
class?: string;
|
||||
children?: JSXElement;
|
||||
fullHeight?: boolean;
|
||||
collapsible?: boolean;
|
||||
}
|
||||
|
||||
export function SidebarLayout(props: SidebarLayoutProps) {
|
||||
const [collapsed, setCollapsed] = createSignal(false);
|
||||
|
||||
const toggleCollapse = () => {
|
||||
if (props.collapsible) {
|
||||
setCollapsed(!collapsed());
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div class={LAYOUT_ROOT + (props.class ? " " + props.class : "")}>
|
||||
<aside class={LAYOUT_SIDEBAR}>
|
||||
<div class={props.fullHeight ? LAYOUT_STICKY_FULL : LAYOUT_STICKY}>
|
||||
{props.sidebar}
|
||||
</div>
|
||||
<Show when={props.collapsible}>
|
||||
<button type="button" onclick={toggleCollapse} aria-label="Toggle sidebar"></button>
|
||||
</Show>
|
||||
</aside>
|
||||
<main class={LAYOUT_MAIN}>
|
||||
{props.children}
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user