"use client"
import * as React from "react"
import { Link, useLocation } from "react-router"
import { Button } from "@/components/ui/button"
import {
ComponentDocImportSection,
DesignSystemDocArticle,
} from "@/components/design-system/component-doc-shell"
import { ComponentDocDetails } from "@/components/design-system/component-doc-details"
import {
DesignSystemPreviewSections,
designSystemHasLivePreview,
} from "@/components/design-system/design-system-previews"
import {
resolveComponentDocSpec,
} from "@/lib/design-system/component-doc-resolve"
import { getComponentDocSpec } 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"
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 = getComponentDocSpec(entry.slug)
const hasLivePreview = 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, and the row key is label + path, so without this a repeat
// renders as two children sharing one key.
const seenImports = new Set()
const relatedImports = [
{ label: entry.name, path: entry.importPath },
...childEntries.map((child) => ({ label: child.name, path: child.importPath })),
...(componentDoc?.extraImports ?? []),
].filter((item) => {
const key = `${item.label}:${item.path}`
if (seenImports.has(key)) return false
seenImports.add(key)
return true
})
useScrollToDocHash(hash, [entry.slug])
return (
{hasLivePreview ? (
) : (
)}
{entry.routeSuffix ? (
) : null}
)
}
export function DesignSystemNotFound({ slug }: { slug: string }) {
const known = getDesignSystemEntry(slug)
if (known) return null
return (