@openpkg-ts/doc-generator 0.5.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -600,6 +600,8 @@ interface FullAPIReferencePageProps {
600
600
  kinds?: SpecExportKind5[];
601
601
  /** Show kind filter buttons (default: true) */
602
602
  showFilters?: boolean;
603
+ /** Show in-page TOC navigation (default: false) */
604
+ showTOC?: boolean;
603
605
  /** Custom title (default: spec.meta.name) */
604
606
  title?: string;
605
607
  /** Custom description */
@@ -613,8 +615,8 @@ interface FullAPIReferencePageProps {
613
615
  *
614
616
  * @example
615
617
  * ```tsx
616
- * // Show all exports
617
- * <FullAPIReferencePage spec={spec} />
618
+ * // Show all exports with TOC
619
+ * <FullAPIReferencePage spec={spec} showTOC />
618
620
  * ```
619
621
  *
620
622
  * @example
@@ -623,7 +625,7 @@ interface FullAPIReferencePageProps {
623
625
  * <FullAPIReferencePage spec={spec} kinds={['function']} title="Functions" />
624
626
  * ```
625
627
  */
626
- declare function FullAPIReferencePage({ spec, kinds, showFilters, title, description, className }: FullAPIReferencePageProps): ReactNode5;
628
+ declare function FullAPIReferencePage({ spec, kinds, showFilters, showTOC, title, description, className }: FullAPIReferencePageProps): ReactNode5;
627
629
  import { OpenPkg as OpenPkg12, SpecExport as SpecExport6 } from "@openpkg-ts/spec";
628
630
  import { ReactNode as ReactNode6 } from "react";
629
631
  interface FunctionPageProps {
@@ -23197,7 +23197,7 @@ function APIPage({
23197
23197
  // src/components/styled/FullAPIReferencePage.tsx
23198
23198
  import { APIReferencePage } from "@doccov/ui/docskit";
23199
23199
  import { cn as cn3 } from "@doccov/ui/lib/utils";
23200
- import { useState as useState5, useMemo as useMemo2 } from "react";
23200
+ import { useState as useState5, useMemo as useMemo2, useEffect as useEffect2, useRef, useCallback } from "react";
23201
23201
 
23202
23202
  // src/components/styled/sections/ExportSection.tsx
23203
23203
  import { jsx as jsx21 } from "react/jsx-runtime";
@@ -23241,15 +23241,33 @@ var KIND_LABELS2 = {
23241
23241
  enum: "Enums",
23242
23242
  variable: "Variables"
23243
23243
  };
23244
+ function getExportTitle(exp) {
23245
+ switch (exp.kind) {
23246
+ case "function":
23247
+ return `${exp.name}()`;
23248
+ case "class":
23249
+ return `class ${exp.name}`;
23250
+ case "interface":
23251
+ case "type":
23252
+ return exp.name;
23253
+ case "enum":
23254
+ return `enum ${exp.name}`;
23255
+ default:
23256
+ return exp.name;
23257
+ }
23258
+ }
23244
23259
  function FullAPIReferencePage({
23245
23260
  spec,
23246
23261
  kinds,
23247
23262
  showFilters = true,
23263
+ showTOC = false,
23248
23264
  title,
23249
23265
  description,
23250
23266
  className
23251
23267
  }) {
23252
23268
  const [activeFilter, setActiveFilter] = useState5("all");
23269
+ const [activeSection, setActiveSection] = useState5(null);
23270
+ const isScrollingRef = useRef(false);
23253
23271
  const availableKinds = useMemo2(() => {
23254
23272
  const kindSet = new Set;
23255
23273
  for (const exp of spec.exports) {
@@ -23277,6 +23295,81 @@ function FullAPIReferencePage({
23277
23295
  return a.name.localeCompare(b.name);
23278
23296
  });
23279
23297
  }, [spec.exports, kinds, activeFilter]);
23298
+ const groupedExports = useMemo2(() => {
23299
+ const groups = new Map;
23300
+ for (const exp of filteredExports) {
23301
+ const kind = exp.kind;
23302
+ if (!groups.has(kind)) {
23303
+ groups.set(kind, []);
23304
+ }
23305
+ groups.get(kind).push(exp);
23306
+ }
23307
+ return groups;
23308
+ }, [filteredExports]);
23309
+ useEffect2(() => {
23310
+ if (typeof window === "undefined")
23311
+ return;
23312
+ const hash = window.location.hash.slice(1);
23313
+ if (hash) {
23314
+ const timer = setTimeout(() => {
23315
+ const element = document.getElementById(hash);
23316
+ if (element) {
23317
+ isScrollingRef.current = true;
23318
+ element.scrollIntoView({ behavior: "smooth" });
23319
+ setActiveSection(hash);
23320
+ setTimeout(() => {
23321
+ isScrollingRef.current = false;
23322
+ }, 1000);
23323
+ }
23324
+ }, 100);
23325
+ return () => clearTimeout(timer);
23326
+ }
23327
+ }, []);
23328
+ useEffect2(() => {
23329
+ if (!showTOC || typeof window === "undefined")
23330
+ return;
23331
+ const sectionIds = filteredExports.map((exp) => exp.id || exp.name);
23332
+ const observers = [];
23333
+ const handleIntersect = (entries) => {
23334
+ if (isScrollingRef.current)
23335
+ return;
23336
+ for (const entry of entries) {
23337
+ if (entry.isIntersecting && entry.intersectionRatio > 0) {
23338
+ const id = entry.target.id;
23339
+ setActiveSection(id);
23340
+ if (typeof window !== "undefined") {
23341
+ window.history.replaceState(null, "", `#${id}`);
23342
+ }
23343
+ break;
23344
+ }
23345
+ }
23346
+ };
23347
+ const observer = new IntersectionObserver(handleIntersect, {
23348
+ rootMargin: "-20% 0px -70% 0px",
23349
+ threshold: 0
23350
+ });
23351
+ for (const id of sectionIds) {
23352
+ const element = document.getElementById(id);
23353
+ if (element) {
23354
+ observer.observe(element);
23355
+ }
23356
+ }
23357
+ return () => {
23358
+ observer.disconnect();
23359
+ };
23360
+ }, [showTOC, filteredExports]);
23361
+ const handleTOCClick = useCallback((id) => {
23362
+ const element = document.getElementById(id);
23363
+ if (element) {
23364
+ isScrollingRef.current = true;
23365
+ element.scrollIntoView({ behavior: "smooth" });
23366
+ setActiveSection(id);
23367
+ window.history.replaceState(null, "", `#${id}`);
23368
+ setTimeout(() => {
23369
+ isScrollingRef.current = false;
23370
+ }, 1000);
23371
+ }
23372
+ }, []);
23280
23373
  const defaultDescription = /* @__PURE__ */ jsxs15("div", {
23281
23374
  children: [
23282
23375
  spec.meta.description && /* @__PURE__ */ jsx22("p", {
@@ -23292,50 +23385,98 @@ function FullAPIReferencePage({
23292
23385
  ]
23293
23386
  });
23294
23387
  const shouldShowFilters = showFilters && !kinds?.length && availableKinds.length > 1;
23295
- return /* @__PURE__ */ jsx22("div", {
23296
- className: cn3("not-prose", className),
23297
- children: /* @__PURE__ */ jsxs15(APIReferencePage, {
23298
- title: title || spec.meta.name || "API Reference",
23299
- description: description || defaultDescription,
23300
- children: [
23301
- shouldShowFilters && /* @__PURE__ */ jsxs15("div", {
23302
- className: "flex flex-wrap gap-2 mb-8 -mt-4",
23388
+ return /* @__PURE__ */ jsxs15("div", {
23389
+ className: cn3("not-prose", showTOC && "lg:grid lg:grid-cols-[220px_1fr] lg:gap-8", className),
23390
+ children: [
23391
+ showTOC && /* @__PURE__ */ jsx22("aside", {
23392
+ className: "hidden lg:block",
23393
+ children: /* @__PURE__ */ jsxs15("nav", {
23394
+ className: "sticky top-20 max-h-[calc(100vh-6rem)] overflow-y-auto pr-4",
23303
23395
  children: [
23304
- /* @__PURE__ */ jsx22("button", {
23305
- type: "button",
23306
- onClick: () => setActiveFilter("all"),
23307
- className: cn3("px-3 py-1.5 text-sm rounded-md transition-all cursor-pointer", activeFilter === "all" ? "bg-primary text-primary-foreground font-medium" : "bg-muted text-muted-foreground hover:bg-muted/80 hover:text-foreground"),
23308
- children: "All"
23396
+ /* @__PURE__ */ jsx22("h4", {
23397
+ className: "text-sm font-semibold text-foreground mb-3",
23398
+ children: "On this page"
23309
23399
  }),
23310
- availableKinds.map((kind) => /* @__PURE__ */ jsx22("button", {
23311
- type: "button",
23312
- onClick: () => setActiveFilter(kind),
23313
- className: cn3("px-3 py-1.5 text-sm rounded-md transition-all cursor-pointer", activeFilter === kind ? "bg-primary text-primary-foreground font-medium" : "bg-muted text-muted-foreground hover:bg-muted/80 hover:text-foreground"),
23314
- children: KIND_LABELS2[kind]
23315
- }, kind))
23400
+ /* @__PURE__ */ jsx22("div", {
23401
+ className: "space-y-4",
23402
+ children: KIND_ORDER2.map((kind) => {
23403
+ const exports = groupedExports.get(kind);
23404
+ if (!exports?.length)
23405
+ return null;
23406
+ return /* @__PURE__ */ jsxs15("div", {
23407
+ children: [
23408
+ /* @__PURE__ */ jsx22("h5", {
23409
+ className: "text-xs font-medium text-muted-foreground uppercase tracking-wide mb-2",
23410
+ children: KIND_LABELS2[kind]
23411
+ }),
23412
+ /* @__PURE__ */ jsx22("ul", {
23413
+ className: "space-y-1",
23414
+ children: exports.map((exp) => {
23415
+ const id = exp.id || exp.name;
23416
+ const isActive = activeSection === id;
23417
+ return /* @__PURE__ */ jsx22("li", {
23418
+ children: /* @__PURE__ */ jsx22("button", {
23419
+ type: "button",
23420
+ onClick: () => handleTOCClick(id),
23421
+ className: cn3("block w-full text-left text-sm py-1 px-2 rounded-md transition-colors cursor-pointer truncate", isActive ? "bg-primary/10 text-primary font-medium" : "text-muted-foreground hover:text-foreground hover:bg-muted/50"),
23422
+ title: getExportTitle(exp),
23423
+ children: getExportTitle(exp)
23424
+ })
23425
+ }, id);
23426
+ })
23427
+ })
23428
+ ]
23429
+ }, kind);
23430
+ })
23431
+ })
23316
23432
  ]
23317
- }),
23318
- filteredExports.map((exp) => /* @__PURE__ */ jsx22(ExportSection, {
23319
- export: exp,
23320
- spec
23321
- }, exp.id || exp.name)),
23322
- filteredExports.length === 0 && /* @__PURE__ */ jsxs15("div", {
23323
- className: "rounded-lg border border-border bg-card/50 p-8 text-center",
23433
+ })
23434
+ }),
23435
+ /* @__PURE__ */ jsx22("div", {
23436
+ children: /* @__PURE__ */ jsxs15(APIReferencePage, {
23437
+ title: title || spec.meta.name || "API Reference",
23438
+ description: description || defaultDescription,
23324
23439
  children: [
23325
- /* @__PURE__ */ jsx22("p", {
23326
- className: "text-muted-foreground",
23327
- children: activeFilter !== "all" ? `No ${KIND_LABELS2[activeFilter].toLowerCase()} found.` : "No exports found in this package."
23440
+ shouldShowFilters && /* @__PURE__ */ jsxs15("div", {
23441
+ className: "flex flex-wrap gap-2 mb-8 -mt-4",
23442
+ children: [
23443
+ /* @__PURE__ */ jsx22("button", {
23444
+ type: "button",
23445
+ onClick: () => setActiveFilter("all"),
23446
+ className: cn3("px-3 py-1.5 text-sm rounded-md transition-all cursor-pointer", activeFilter === "all" ? "bg-primary text-primary-foreground font-medium" : "bg-muted text-muted-foreground hover:bg-muted/80 hover:text-foreground"),
23447
+ children: "All"
23448
+ }),
23449
+ availableKinds.map((kind) => /* @__PURE__ */ jsx22("button", {
23450
+ type: "button",
23451
+ onClick: () => setActiveFilter(kind),
23452
+ className: cn3("px-3 py-1.5 text-sm rounded-md transition-all cursor-pointer", activeFilter === kind ? "bg-primary text-primary-foreground font-medium" : "bg-muted text-muted-foreground hover:bg-muted/80 hover:text-foreground"),
23453
+ children: KIND_LABELS2[kind]
23454
+ }, kind))
23455
+ ]
23328
23456
  }),
23329
- activeFilter !== "all" && /* @__PURE__ */ jsx22("button", {
23330
- type: "button",
23331
- onClick: () => setActiveFilter("all"),
23332
- className: "mt-3 text-sm text-primary hover:underline cursor-pointer",
23333
- children: "Show all exports"
23457
+ filteredExports.map((exp) => /* @__PURE__ */ jsx22(ExportSection, {
23458
+ export: exp,
23459
+ spec
23460
+ }, exp.id || exp.name)),
23461
+ filteredExports.length === 0 && /* @__PURE__ */ jsxs15("div", {
23462
+ className: "rounded-lg border border-border bg-card/50 p-8 text-center",
23463
+ children: [
23464
+ /* @__PURE__ */ jsx22("p", {
23465
+ className: "text-muted-foreground",
23466
+ children: activeFilter !== "all" ? `No ${KIND_LABELS2[activeFilter].toLowerCase()} found.` : "No exports found in this package."
23467
+ }),
23468
+ activeFilter !== "all" && /* @__PURE__ */ jsx22("button", {
23469
+ type: "button",
23470
+ onClick: () => setActiveFilter("all"),
23471
+ className: "mt-3 text-sm text-primary hover:underline cursor-pointer",
23472
+ children: "Show all exports"
23473
+ })
23474
+ ]
23334
23475
  })
23335
23476
  ]
23336
23477
  })
23337
- ]
23338
- })
23478
+ })
23479
+ ]
23339
23480
  });
23340
23481
  }
23341
23482
  // src/components/styled/ParameterItem.tsx
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openpkg-ts/doc-generator",
3
- "version": "0.5.1",
3
+ "version": "0.6.0",
4
4
  "description": "API doc generator consuming OpenPkg specs. TypeDoc alternative for modern doc frameworks.",
5
5
  "keywords": [
6
6
  "openpkg",