Files
paseo/packages/website/src/routes/docs.tsx
T
Mohamed Boudra 29ce6653fd Improve mobile selectors and sheet inputs
Keep sheet text inputs native-owned to avoid React Native controlled input flicker during fast typing. Add reset keys for fields that need programmatic clears, and move model, mode, and thinking pickers onto shared combobox rows with fuzzy ranked search and native virtualization.
2026-05-18 14:56:17 +07:00

136 lines
4.8 KiB
TypeScript

import { createFileRoute, Link, Outlet } from "@tanstack/react-router";
import { Menu, X } from "lucide-react";
import { useCallback, useState } from "react";
import { type Doc, getDocs } from "~/docs";
import "~/styles.css";
export const Route = createFileRoute("/docs")({
component: DocsLayout,
});
const ACTIVE_OPTIONS_EXACT = { exact: true };
const MOBILE_ACTIVE_PROPS = { className: "text-foreground" };
const DESKTOP_ACTIVE_PROPS = { className: "bg-muted text-foreground" };
interface NavItem {
name: string;
href: string;
}
interface NavGroup {
name: string | null;
items: NavItem[];
}
const GROUP_LABELS: Record<string, string> = {
alternatives: "Alternatives",
};
function groupKey(doc: Doc): string | null {
const idx = doc.slug.indexOf("/");
return idx === -1 ? null : doc.slug.slice(0, idx);
}
function buildNavigation(): NavGroup[] {
const groups = new Map<string | null, NavItem[]>();
for (const doc of getDocs()) {
const key = groupKey(doc);
const items = groups.get(key) ?? [];
items.push({ name: doc.frontmatter.nav, href: doc.href });
groups.set(key, items);
}
const ordered: NavGroup[] = [];
if (groups.has(null)) ordered.push({ name: null, items: groups.get(null)! });
for (const [key, items] of groups) {
if (key === null) continue;
ordered.push({ name: GROUP_LABELS[key] ?? key, items });
}
return ordered;
}
function DocsLayout() {
const groups = buildNavigation();
const [mobileNavOpen, setMobileNavOpen] = useState(false);
const toggleMobileNav = useCallback(() => setMobileNavOpen((v) => !v), []);
const closeMobileNav = useCallback(() => setMobileNavOpen(false), []);
return (
<div className="min-h-screen bg-background">
{/* Mobile header */}
<header className="md:hidden sticky top-0 z-50 bg-background border-b border-border">
<div className="flex items-center justify-between p-4">
<Link to="/" className="flex items-center gap-3">
<img src="/logo.svg" alt="Paseo" className="w-6 h-6" />
<span className="text-lg font-medium">Paseo</span>
</Link>
<button
type="button"
onClick={toggleMobileNav}
aria-label={mobileNavOpen ? "Close menu" : "Open menu"}
aria-expanded={mobileNavOpen}
className="-mr-2 p-2 text-muted-foreground hover:text-foreground transition-colors"
>
{mobileNavOpen ? <X size={20} /> : <Menu size={20} />}
</button>
</div>
{mobileNavOpen && (
<nav className="border-t border-border px-4 py-4 space-y-4 max-h-[calc(100dvh-4rem)] overflow-y-auto">
{groups.map((group) => (
<div key={group.name ?? "root"} className="space-y-1">
{group.name && (
<div className="text-sm font-medium text-foreground mb-2">{group.name}</div>
)}
{group.items.map((item) => (
<Link
key={item.href}
to={item.href}
activeOptions={ACTIVE_OPTIONS_EXACT}
onClick={closeMobileNav}
className="block py-2 text-sm text-muted-foreground hover:text-foreground transition-colors"
activeProps={MOBILE_ACTIVE_PROPS}
>
{item.name}
</Link>
))}
</div>
))}
</nav>
)}
</header>
<div className="flex">
{/* Desktop sidebar */}
<aside className="hidden md:flex md:flex-col w-56 shrink-0 border-r border-border p-6 sticky top-0 h-screen">
<Link to="/" className="flex items-center gap-3 mb-8 shrink-0">
<img src="/logo.svg" alt="Paseo" className="w-6 h-6" />
<span className="text-lg font-medium">Paseo</span>
</Link>
<nav className="flex-1 min-h-0 overflow-y-auto -ml-3 -mr-3 pr-3 space-y-4">
{groups.map((group) => (
<div key={group.name ?? "root"} className="space-y-1">
{group.name && (
<div className="px-3 py-2 text-sm font-medium text-foreground">{group.name}</div>
)}
{group.items.map((item) => (
<Link
key={item.href}
to={item.href}
activeOptions={ACTIVE_OPTIONS_EXACT}
className="block px-3 py-2 text-sm rounded-md text-muted-foreground hover:text-foreground hover:bg-muted transition-colors"
activeProps={DESKTOP_ACTIVE_PROPS}
>
{item.name}
</Link>
))}
</div>
))}
</nav>
</aside>
<main className="flex-1 min-w-0 p-6 md:p-12 max-w-3xl docs-prose">
<Outlet />
</main>
</div>
</div>
);
}