"use client";
import * as React from "react";
import { Link, useLocation } from "react-router";
import { Button } from "@/components/ui/button";
import {
ComponentDocImportSection,
ComponentDocSpecSections,
DesignSystemDocArticle,
} from "@/components/design-system/component-doc-shell";
import { ComponentDocDetails } from "@/components/design-system/component-doc-details";
import { designSystemHasLivePreview } from "@/components/design-system/design-system-preview-meta";
import { resolveComponentDocSpec } from "@/lib/design-system/component-doc-resolve";
import { designSystemPreviewIsWide } from "@/lib/design-system/component-doc-shell";
import { loadComponentDocSpec } from "@/lib/design-system/component-docs";
import {
getDesignSystemChildEntries,
getDesignSystemEntry,
type DesignSystemRegistryEntry,
} from "@/lib/design-system/registry";
import {
DS_DOC_BODY,
DS_DOC_BODY_EMPHASIS,
} from "@/lib/design-system/doc-typography";
import { cn } from "@/lib/utils";
import { useProductDashboardHref } from "@/contexts/product-route-sync";
const FallbackPreviewSections = React.lazy(() =>
import("@/components/design-system/design-system-previews").then((mod) => ({
default: mod.DesignSystemPreviewSections,
})),
);
const PREVIEW_FALLBACK = (
);
function PreviewPlaceholder({ entry }: { entry: DesignSystemRegistryEntry }) {
return (
Example preview
Skeleton slot for{" "}
{entry.name}. Wire live
demos here.
);
}
/**
* Client-side nav (react-router ``, unlike a real page load) never
* scrolls to a URL hash on its own, so a folded-child link like
* `marketing-banner#banner` would land on top of the Banner page with no
* visible sign it did anything more specific than the Banner link. Once the
* doc's sections (or anatomy rows) have painted, find the matching `id` and
* bring it into view.
*/
function useScrollToDocHash(hash: string, deps: readonly unknown[]) {
React.useEffect(() => {
if (!hash) return;
const id = hash.slice(1);
if (!id) return;
const raf = requestAnimationFrame(() => {
document.getElementById(id)?.scrollIntoView({ block: "start" });
});
return () => cancelAnimationFrame(raf);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [hash, ...deps]);
}
export function DesignSystemDocPage({
entry,
}: {
entry: DesignSystemRegistryEntry;
}) {
const dashboardHref = useProductDashboardHref();
const { hash } = useLocation();
const productBase = dashboardHref.replace(/\/dashboard$/, "");
const componentDoc = React.use(loadComponentDocSpec(entry.slug));
const hasSpecSections = Boolean(componentDoc?.sections.length);
const hasLivePreview =
hasSpecSections || designSystemHasLivePreview(entry.slug);
const childEntries = getDesignSystemChildEntries(entry.slug);
const skeletonDoc = React.useMemo(
() => resolveComponentDocSpec(entry, childEntries, componentDoc),
[componentDoc, entry, childEntries],
);
// The entry, its folded children, and the doc's extras are free to name the
// same module. Dedupe by normalized path so `…/components/status-badge` and
// `…/components/ui/status-badge` (same export via package `./components/*`)
// do not render as two import rows.
const seenImports = new Set();
const relatedImports = [
...(entry.importPath
? [{ label: entry.name, path: entry.importPath }]
: []),
...childEntries.flatMap((child) =>
child.importPath ? [{ label: child.name, path: child.importPath }] : [],
),
...(componentDoc?.extraImports ?? []),
].filter((item) => {
const key = item.path
.replace(
/@exxatdesignux\/ui\/components\/ui\//,
"@exxatdesignux/ui/components/",
)
.replace(/@\/components\/ui\//, "@exxatdesignux/ui/components/")
.replace(
/@\/components\/data-views.*/,
"@exxatdesignux/ui/components/data-views",
);
if (seenImports.has(key)) return false;
seenImports.add(key);
return true;
});
useScrollToDocHash(hash, [entry.slug]);
return (
{hasSpecSections && componentDoc ? (
) : hasLivePreview ? (
) : (
)}
{entry.routeAbsolute || entry.routeSuffix ? (
) : null}
);
}
export function DesignSystemNotFound({ slug }: { slug: string }) {
const known = getDesignSystemEntry(slug);
if (known) return null;
return (
Not in registry
No registry entry for {slug}.
);
}