Files
Ben Sabic b0ab804f18 - Bundle guide markdown and a templates manifest with the chat package at resources/guides/*.md and resources/templates.json so AI agents can discover Chat SDK resources offline (#423)
- Add scripts/sync-resources.ts (run via pnpm sync-resources) that reads apps/docs/resources-edge-config.json, fetches each guide's .md version over https with a timeout and size cap, writes templates.json, and regenerates the Available resources block in skills/chat/SKILL.md
- Migrate the Slack Next.js, Discord Nuxt, and Hono code-review guides from on-site MDX to Vercel KB and register them in the resources edge-config JSON alongside the existing external guides
- Remove /docs/guides MDX content, sidebar entries, top-level Guides nav entry, getting-started cards, and the dead /guides/ branch in the sitemap route now that all guides live externally and are surfaced on /resources
- Replace the homepage Guides/Templates section and the standalone Adapters pill section with a single two-column Resources + Adapters section (icons, headings, descriptions, outline buttons, divider), and drop the URL footer from ResourceCard on the Resources page
- Update skills/chat/SKILL.md to point at resources/guides and resources/templates.json and list the available guides and templates between marker comments that sync-resources rewrites
- Add tsx to knip's ignoreBinaries so npx tsx in the new script does not fail lint

Co-authored-by: Ben Sabic <bensabic@users.noreply.github.com>
Co-authored-by: dancer <josh@afterima.ge>
2026-04-27 10:20:44 +10:00

82 lines
2.8 KiB
TypeScript

"use client";
import DynamicLink from "fumadocs-core/dynamic-link";
import { usePathname } from "next/navigation";
import { IconArrowUpRightSmall } from "@/components/geistcn-fallbacks/geistcn-assets/icons/icon-arrow-up-right-small";
import {
NavigationMenu,
NavigationMenuItem,
NavigationMenuLink,
NavigationMenuList,
} from "@/components/ui/navigation-menu";
import { useIsMobile } from "@/hooks/use-mobile";
import { cn } from "@/lib/utils";
interface DesktopMenuProps {
className?: string;
items: { label: string; href: string }[];
}
// Nav hrefs can be deep links (e.g. `/docs/api/chat`), but the tab
// represents the whole section. Use the first three path segments as
// the matching prefix.
const getSectionPrefix = (href: string) =>
href.split("/").slice(0, 3).join("/");
export const DesktopMenu = ({ items, className }: DesktopMenuProps) => {
const isMobile = useIsMobile();
const pathname = usePathname();
// Pick the single longest-matching section (so `Docs` doesn't stay
// active when a more specific section like `API` also matches).
const activeHref = items.reduce<string | null>((winner, item) => {
if (item.href.startsWith("http")) return winner;
const prefix = getSectionPrefix(item.href);
const matches =
pathname.endsWith(prefix) || pathname.includes(`${prefix}/`);
if (!matches) return winner;
if (!winner) return item.href;
return getSectionPrefix(item.href).length >
getSectionPrefix(winner).length
? item.href
: winner;
}, null);
return (
<NavigationMenu viewport={isMobile}>
<NavigationMenuList className={cn("h-14 gap-4", className)}>
{items.map((item) => {
const isExternal = item.href.startsWith("http");
const isActive = !isExternal && item.href === activeHref;
return (
<NavigationMenuItem key={item.href}>
<NavigationMenuLink
active={isActive}
asChild
className="flex items-center text-gray-900 text-sm transition-colors duration-100 hover:text-gray-1000 data-[active]:text-gray-1000"
>
{isExternal ? (
<a
className="flex flex-row items-center gap-1"
href={item.href}
rel="noopener"
target="_blank"
>
{item.label}
<IconArrowUpRightSmall aria-hidden="true" size={12} />
</a>
) : (
<DynamicLink href={`/[lang]${item.href}`}>
{item.label}
</DynamicLink>
)}
</NavigationMenuLink>
</NavigationMenuItem>
);
})}
</NavigationMenuList>
</NavigationMenu>
);
};