@openpkg-ts/doc-generator 0.5.1 → 0.6.1

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 {
@@ -22667,6 +22667,14 @@ var KIND_LABELS = {
22667
22667
  enum: "Enums",
22668
22668
  variable: "Variables"
22669
22669
  };
22670
+ var KIND_SLUGS = {
22671
+ function: "functions",
22672
+ class: "classes",
22673
+ interface: "interfaces",
22674
+ type: "types",
22675
+ enum: "enums",
22676
+ variable: "variables"
22677
+ };
22670
22678
  function groupByKind(exports) {
22671
22679
  const groups = new Map;
22672
22680
  for (const exp of exports) {
@@ -22780,7 +22788,7 @@ function ExportIndexPage({
22780
22788
  children: group.exports.map((exp) => /* @__PURE__ */ jsx13(ExportCard, {
22781
22789
  name: exp.name,
22782
22790
  description: exp.description,
22783
- href: `${baseHref}/${group.kind}s/${exp.id}`,
22791
+ href: `${baseHref}/${KIND_SLUGS[group.kind]}/${exp.id}`,
22784
22792
  kind: exp.kind
22785
22793
  }, exp.id))
22786
22794
  })
@@ -23197,7 +23205,7 @@ function APIPage({
23197
23205
  // src/components/styled/FullAPIReferencePage.tsx
23198
23206
  import { APIReferencePage } from "@doccov/ui/docskit";
23199
23207
  import { cn as cn3 } from "@doccov/ui/lib/utils";
23200
- import { useState as useState5, useMemo as useMemo2 } from "react";
23208
+ import { useState as useState5, useMemo as useMemo2, useEffect as useEffect2, useRef, useCallback } from "react";
23201
23209
 
23202
23210
  // src/components/styled/sections/ExportSection.tsx
23203
23211
  import { jsx as jsx21 } from "react/jsx-runtime";
@@ -23241,15 +23249,33 @@ var KIND_LABELS2 = {
23241
23249
  enum: "Enums",
23242
23250
  variable: "Variables"
23243
23251
  };
23252
+ function getExportTitle(exp) {
23253
+ switch (exp.kind) {
23254
+ case "function":
23255
+ return `${exp.name}()`;
23256
+ case "class":
23257
+ return `class ${exp.name}`;
23258
+ case "interface":
23259
+ case "type":
23260
+ return exp.name;
23261
+ case "enum":
23262
+ return `enum ${exp.name}`;
23263
+ default:
23264
+ return exp.name;
23265
+ }
23266
+ }
23244
23267
  function FullAPIReferencePage({
23245
23268
  spec,
23246
23269
  kinds,
23247
23270
  showFilters = true,
23271
+ showTOC = false,
23248
23272
  title,
23249
23273
  description,
23250
23274
  className
23251
23275
  }) {
23252
23276
  const [activeFilter, setActiveFilter] = useState5("all");
23277
+ const [activeSection, setActiveSection] = useState5(null);
23278
+ const isScrollingRef = useRef(false);
23253
23279
  const availableKinds = useMemo2(() => {
23254
23280
  const kindSet = new Set;
23255
23281
  for (const exp of spec.exports) {
@@ -23277,6 +23303,81 @@ function FullAPIReferencePage({
23277
23303
  return a.name.localeCompare(b.name);
23278
23304
  });
23279
23305
  }, [spec.exports, kinds, activeFilter]);
23306
+ const groupedExports = useMemo2(() => {
23307
+ const groups = new Map;
23308
+ for (const exp of filteredExports) {
23309
+ const kind = exp.kind;
23310
+ if (!groups.has(kind)) {
23311
+ groups.set(kind, []);
23312
+ }
23313
+ groups.get(kind).push(exp);
23314
+ }
23315
+ return groups;
23316
+ }, [filteredExports]);
23317
+ useEffect2(() => {
23318
+ if (typeof window === "undefined")
23319
+ return;
23320
+ const hash = window.location.hash.slice(1);
23321
+ if (hash) {
23322
+ const timer = setTimeout(() => {
23323
+ const element = document.getElementById(hash);
23324
+ if (element) {
23325
+ isScrollingRef.current = true;
23326
+ element.scrollIntoView({ behavior: "smooth" });
23327
+ setActiveSection(hash);
23328
+ setTimeout(() => {
23329
+ isScrollingRef.current = false;
23330
+ }, 1000);
23331
+ }
23332
+ }, 100);
23333
+ return () => clearTimeout(timer);
23334
+ }
23335
+ }, []);
23336
+ useEffect2(() => {
23337
+ if (!showTOC || typeof window === "undefined")
23338
+ return;
23339
+ const sectionIds = filteredExports.map((exp) => exp.id || exp.name);
23340
+ const observers = [];
23341
+ const handleIntersect = (entries) => {
23342
+ if (isScrollingRef.current)
23343
+ return;
23344
+ for (const entry of entries) {
23345
+ if (entry.isIntersecting && entry.intersectionRatio > 0) {
23346
+ const id = entry.target.id;
23347
+ setActiveSection(id);
23348
+ if (typeof window !== "undefined") {
23349
+ window.history.replaceState(null, "", `#${id}`);
23350
+ }
23351
+ break;
23352
+ }
23353
+ }
23354
+ };
23355
+ const observer = new IntersectionObserver(handleIntersect, {
23356
+ rootMargin: "-20% 0px -70% 0px",
23357
+ threshold: 0
23358
+ });
23359
+ for (const id of sectionIds) {
23360
+ const element = document.getElementById(id);
23361
+ if (element) {
23362
+ observer.observe(element);
23363
+ }
23364
+ }
23365
+ return () => {
23366
+ observer.disconnect();
23367
+ };
23368
+ }, [showTOC, filteredExports]);
23369
+ const handleTOCClick = useCallback((id) => {
23370
+ const element = document.getElementById(id);
23371
+ if (element) {
23372
+ isScrollingRef.current = true;
23373
+ element.scrollIntoView({ behavior: "smooth" });
23374
+ setActiveSection(id);
23375
+ window.history.replaceState(null, "", `#${id}`);
23376
+ setTimeout(() => {
23377
+ isScrollingRef.current = false;
23378
+ }, 1000);
23379
+ }
23380
+ }, []);
23280
23381
  const defaultDescription = /* @__PURE__ */ jsxs15("div", {
23281
23382
  children: [
23282
23383
  spec.meta.description && /* @__PURE__ */ jsx22("p", {
@@ -23292,50 +23393,98 @@ function FullAPIReferencePage({
23292
23393
  ]
23293
23394
  });
23294
23395
  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",
23396
+ return /* @__PURE__ */ jsxs15("div", {
23397
+ className: cn3("not-prose", showTOC && "lg:grid lg:grid-cols-[220px_1fr] lg:gap-8", className),
23398
+ children: [
23399
+ showTOC && /* @__PURE__ */ jsx22("aside", {
23400
+ className: "hidden lg:block",
23401
+ children: /* @__PURE__ */ jsxs15("nav", {
23402
+ className: "sticky top-20 max-h-[calc(100vh-6rem)] overflow-y-auto pr-4",
23303
23403
  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"
23404
+ /* @__PURE__ */ jsx22("h4", {
23405
+ className: "text-sm font-semibold text-foreground mb-3",
23406
+ children: "On this page"
23309
23407
  }),
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))
23408
+ /* @__PURE__ */ jsx22("div", {
23409
+ className: "space-y-4",
23410
+ children: KIND_ORDER2.map((kind) => {
23411
+ const exports = groupedExports.get(kind);
23412
+ if (!exports?.length)
23413
+ return null;
23414
+ return /* @__PURE__ */ jsxs15("div", {
23415
+ children: [
23416
+ /* @__PURE__ */ jsx22("h5", {
23417
+ className: "text-xs font-medium text-muted-foreground uppercase tracking-wide mb-2",
23418
+ children: KIND_LABELS2[kind]
23419
+ }),
23420
+ /* @__PURE__ */ jsx22("ul", {
23421
+ className: "space-y-1",
23422
+ children: exports.map((exp) => {
23423
+ const id = exp.id || exp.name;
23424
+ const isActive = activeSection === id;
23425
+ return /* @__PURE__ */ jsx22("li", {
23426
+ children: /* @__PURE__ */ jsx22("button", {
23427
+ type: "button",
23428
+ onClick: () => handleTOCClick(id),
23429
+ 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"),
23430
+ title: getExportTitle(exp),
23431
+ children: getExportTitle(exp)
23432
+ })
23433
+ }, id);
23434
+ })
23435
+ })
23436
+ ]
23437
+ }, kind);
23438
+ })
23439
+ })
23316
23440
  ]
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",
23441
+ })
23442
+ }),
23443
+ /* @__PURE__ */ jsx22("div", {
23444
+ children: /* @__PURE__ */ jsxs15(APIReferencePage, {
23445
+ title: title || spec.meta.name || "API Reference",
23446
+ description: description || defaultDescription,
23324
23447
  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."
23448
+ shouldShowFilters && /* @__PURE__ */ jsxs15("div", {
23449
+ className: "flex flex-wrap gap-2 mb-8 -mt-4",
23450
+ children: [
23451
+ /* @__PURE__ */ jsx22("button", {
23452
+ type: "button",
23453
+ onClick: () => setActiveFilter("all"),
23454
+ 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"),
23455
+ children: "All"
23456
+ }),
23457
+ availableKinds.map((kind) => /* @__PURE__ */ jsx22("button", {
23458
+ type: "button",
23459
+ onClick: () => setActiveFilter(kind),
23460
+ 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"),
23461
+ children: KIND_LABELS2[kind]
23462
+ }, kind))
23463
+ ]
23328
23464
  }),
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"
23465
+ filteredExports.map((exp) => /* @__PURE__ */ jsx22(ExportSection, {
23466
+ export: exp,
23467
+ spec
23468
+ }, exp.id || exp.name)),
23469
+ filteredExports.length === 0 && /* @__PURE__ */ jsxs15("div", {
23470
+ className: "rounded-lg border border-border bg-card/50 p-8 text-center",
23471
+ children: [
23472
+ /* @__PURE__ */ jsx22("p", {
23473
+ className: "text-muted-foreground",
23474
+ children: activeFilter !== "all" ? `No ${KIND_LABELS2[activeFilter].toLowerCase()} found.` : "No exports found in this package."
23475
+ }),
23476
+ activeFilter !== "all" && /* @__PURE__ */ jsx22("button", {
23477
+ type: "button",
23478
+ onClick: () => setActiveFilter("all"),
23479
+ className: "mt-3 text-sm text-primary hover:underline cursor-pointer",
23480
+ children: "Show all exports"
23481
+ })
23482
+ ]
23334
23483
  })
23335
23484
  ]
23336
23485
  })
23337
- ]
23338
- })
23486
+ })
23487
+ ]
23339
23488
  });
23340
23489
  }
23341
23490
  // 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.1",
4
4
  "description": "API doc generator consuming OpenPkg specs. TypeDoc alternative for modern doc frameworks.",
5
5
  "keywords": [
6
6
  "openpkg",