forgedb 0.3.1

ForgeDB — an application database generator. Compiles a declarative .forge schema into tailored Rust database code, a TypeScript SDK, and a REST API.
Documentation
"use client";

import { useEffect, useState } from "react";
import { usePathname, useRouter, useSearchParams } from "next/navigation";
import { useAtom } from "jotai";
import {
  DEFAULT_ECOSYSTEM,
  ECOSYSTEMS,
  ecosystemAtom,
  isEcosystem,
  type Ecosystem,
} from "@/lib/atoms";
import { cn } from "@/lib/utils";

const LABELS: Record<Ecosystem, string> = {
  node: "Node / Bun",
  python: "Python",
  rust: "Rust",
  go: "Go",
};

/**
 * The reader's global ecosystem switch (Node/Bun · Python · Rust · Go). Sets the
 * sticky `ecosystemAtom`; every `<Eco>` block on the page follows, swapping the
 * suggested install command and the runtime/SDK usage examples to the selected
 * language. Schema and generated-code samples never change — they're
 * language-agnostic.
 *
 * Persisted two ways: `atomWithStorage` (localStorage, sticky across pages and
 * sessions) and a `?eco=` query param (shareable deep links). A `?eco=` present
 * on load wins over the stored preference. Reads `useSearchParams`, so it must
 * render inside a Suspense boundary (see the doc page). Rendered only on pages
 * that actually carry `<Eco>` blocks — the doc page computes that and omits the
 * control otherwise, so it never appears where it would do nothing.
 */
export function EcosystemToggle() {
  const [eco, setEco] = useAtom(ecosystemAtom);
  const [mounted, setMounted] = useState(false);
  const searchParams = useSearchParams();
  const pathname = usePathname();
  const router = useRouter();

  // A `?eco=` deep link overrides the stored preference on first load only.
  useEffect(() => {
    setMounted(true);
    const q = searchParams.get("eco");
    if (q && isEcosystem(q) && q !== eco) setEco(q);
    // Run once on mount; intentionally not reacting to later eco/searchParams changes.
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  const select = (value: Ecosystem) => {
    setEco(value);
    // Reflect the choice in the URL (shareable) without scrolling or a nav.
    const params = new URLSearchParams(searchParams.toString());
    params.set("eco", value);
    router.replace(`${pathname}?${params.toString()}`, { scroll: false });
  };

  // Before hydration, render the default so SSR and the first client render
  // agree (localStorage isn't readable server-side).
  const active = mounted ? eco : DEFAULT_ECOSYSTEM;

  return (
    <div
      role="radiogroup"
      aria-label="Language ecosystem"
      className="inline-flex items-center gap-0.5 rounded-lg border border-border/60 bg-muted/40 p-0.5 text-sm"
    >
      {ECOSYSTEMS.map((value) => {
        const isActive = active === value;
        return (
          <button
            key={value}
            type="button"
            role="radio"
            aria-checked={isActive}
            onClick={() => select(value)}
            className={cn(
              "rounded-md px-2.5 py-1 transition-colors",
              isActive
                ? "bg-background font-medium text-foreground shadow-sm"
                : "text-muted-foreground hover:text-foreground",
            )}
          >
            {LABELS[value]}
          </button>
        );
      })}
    </div>
  );
}