@jfrader/storybook-docs 0.9.2 → 0.10.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.
@@ -1,7 +1,11 @@
1
1
  import type { ComponentPropsDoc, PropDoc } from '../prop-docs.js';
2
2
  export declare function toPropDoc(prop: unknown): PropDoc;
3
3
  export declare function createPropsCollector(): {
4
- collect: (storyFile: string | undefined, entryName: string, slug: string) => ComponentPropsDoc[];
4
+ collect: (storyFile: string | undefined, entryName: string, slug: string, bases?: {
5
+ name: string;
6
+ slug: string;
7
+ storyFile: string;
8
+ }[]) => ComponentPropsDoc[];
5
9
  setBuildMode(value: boolean): void;
6
10
  clearPrograms(): void;
7
11
  };
package/dist/index.js CHANGED
@@ -13,6 +13,15 @@ import { pages, site as site2, theme } from "virtual:storybook-docs/site";
13
13
  import { useEffect as useEffect6, useMemo as useMemo3, useState as useState7 } from "react";
14
14
  import { storySource as storySource2 } from "virtual:storybook-docs/catalog";
15
15
 
16
+ // src/base.ts
17
+ var STORYBOOK_DOCS_BASE = (() => {
18
+ const globalBase = globalThis.__STORYBOOK_DOCS_BASE__;
19
+ if (globalBase) return globalBase.replace(/\/$/, "");
20
+ const { pathname } = window.location;
21
+ return pathname === "/" || pathname.endsWith(".html") ? "" : pathname.replace(/\/$/, "");
22
+ })();
23
+ var hrefFor = (path) => `${STORYBOOK_DOCS_BASE}${path}`;
24
+
16
25
  // src/guidance.tsx
17
26
  import { createElement, useEffect, useMemo as useMemo2, useState } from "react";
18
27
  import { loadGuidance } from "virtual:storybook-docs/guidance";
@@ -1156,7 +1165,7 @@ function formatValue(value) {
1156
1165
  return String(value);
1157
1166
  }
1158
1167
  function TableFrame({ label, children }) {
1159
- return /* @__PURE__ */ jsx8("div", { className: "table-scroll", role: "region", "aria-label": label, tabIndex: 0, children: /* @__PURE__ */ jsxs5("table", { children: [
1168
+ return /* @__PURE__ */ jsx8("div", { className: "table-scroll", role: "region", "aria-label": label, tabIndex: 0, children: /* @__PURE__ */ jsxs5("table", { className: "props-table", children: [
1160
1169
  /* @__PURE__ */ jsx8("caption", { className: "sr-only", children: label }),
1161
1170
  children
1162
1171
  ] }) });
@@ -1175,28 +1184,67 @@ function PropNotes({ prop }) {
1175
1184
  !prop.description && !prop.tags?.length && !prop.inheritedFrom ? "\u2014" : null
1176
1185
  ] });
1177
1186
  }
1187
+ var SHAPE_PREVIEW_FIELDS = 3;
1188
+ function PropType({ prop }) {
1189
+ const signature2 = prop.signatures?.[0];
1190
+ const fields = prop.members?.slice(0, SHAPE_PREVIEW_FIELDS).map((member) => `${member.name}${member.required ? "" : "?"}: \u2026`).join("; ");
1191
+ return /* @__PURE__ */ jsxs5(Fragment3, { children: [
1192
+ /* @__PURE__ */ jsx8("code", { className: signature2 ? "type signature" : "type", tabIndex: signature2 ? 0 : void 0, children: prop.typeUnavailable ? "Not resolved" : signature2?.type ?? prop.type }),
1193
+ signature2 ? /* @__PURE__ */ jsxs5("details", { children: [
1194
+ /* @__PURE__ */ jsx8("summary", { children: "Signature details" }),
1195
+ prop.type !== signature2.type ? /* @__PURE__ */ jsx8("code", { className: "type", children: prop.type }) : null,
1196
+ prop.signatures?.map((item, index) => /* @__PURE__ */ jsxs5("div", { children: [
1197
+ /* @__PURE__ */ jsx8("code", { className: "type", children: item.type }),
1198
+ /* @__PURE__ */ jsx8("dl", { children: item.parameters.filter((parameter) => parameter.description).map((parameter) => /* @__PURE__ */ jsxs5("div", { children: [
1199
+ /* @__PURE__ */ jsx8("dt", { children: /* @__PURE__ */ jsxs5("code", { children: [
1200
+ parameter.name,
1201
+ ": ",
1202
+ parameter.type
1203
+ ] }) }),
1204
+ /* @__PURE__ */ jsx8("dd", { children: /* @__PURE__ */ jsx8(MarkdownView, { markdown: parameter.description }) })
1205
+ ] }, parameter.name)) })
1206
+ ] }, index))
1207
+ ] }) : null,
1208
+ prop.members?.length ? /* @__PURE__ */ jsxs5("details", { children: [
1209
+ /* @__PURE__ */ jsxs5("summary", { children: [
1210
+ prop.name === "slotProps" ? "Slots" : "Fields",
1211
+ " ",
1212
+ /* @__PURE__ */ jsx8("code", { children: `{ ${fields}${prop.members.length > SHAPE_PREVIEW_FIELDS ? "; \u2026" : ""} }` })
1213
+ ] }),
1214
+ /* @__PURE__ */ jsx8("dl", { children: prop.members.map((member) => /* @__PURE__ */ jsxs5("div", { children: [
1215
+ /* @__PURE__ */ jsxs5("dt", { children: [
1216
+ /* @__PURE__ */ jsxs5("code", { children: [
1217
+ member.name,
1218
+ member.required ? "" : "?"
1219
+ ] }),
1220
+ member.required ? " (required)" : ""
1221
+ ] }),
1222
+ /* @__PURE__ */ jsxs5("dd", { children: [
1223
+ /* @__PURE__ */ jsx8(PropType, { prop: member }),
1224
+ /* @__PURE__ */ jsx8(PropNotes, { prop: member })
1225
+ ] })
1226
+ ] }, member.name)) })
1227
+ ] }) : null
1228
+ ] });
1229
+ }
1230
+ function InheritedLinks({ component }) {
1231
+ const bases = Array.from(new Map(component.props.flatMap((prop) => prop.inheritedComponent ? [[prop.inheritedComponent.slug, prop.inheritedComponent]] : [])).values());
1232
+ return bases.length ? /* @__PURE__ */ jsxs5("p", { children: [
1233
+ "Also available from ",
1234
+ bases.map((entry, index) => /* @__PURE__ */ jsxs5("span", { children: [
1235
+ index ? ", " : "",
1236
+ /* @__PURE__ */ jsx8("a", { href: `#${hrefFor(`/components/${entry.slug}`)}`, children: entry.name })
1237
+ ] }, entry.slug)),
1238
+ "."
1239
+ ] }) : null;
1240
+ }
1178
1241
  function PropRows({ props }) {
1179
1242
  return props.map((prop) => /* @__PURE__ */ jsxs5("tr", { children: [
1180
1243
  /* @__PURE__ */ jsxs5("th", { scope: "row", children: [
1181
1244
  /* @__PURE__ */ jsx8("code", { children: prop.name }),
1182
1245
  prop.required ? /* @__PURE__ */ jsx8("span", { className: "req", children: "required" }) : null
1183
1246
  ] }),
1184
- /* @__PURE__ */ jsxs5("td", { children: [
1185
- /* @__PURE__ */ jsx8("code", { className: "type", children: prop.typeUnavailable ? "Not resolved" : prop.type }),
1186
- prop.members?.length ? /* @__PURE__ */ jsxs5("details", { children: [
1187
- /* @__PURE__ */ jsx8("summary", { children: prop.name === "slotProps" ? "Slots" : "Fields" }),
1188
- /* @__PURE__ */ jsx8("dl", { children: prop.members.map((member) => /* @__PURE__ */ jsxs5("div", { children: [
1189
- /* @__PURE__ */ jsxs5("dt", { children: [
1190
- /* @__PURE__ */ jsx8("code", { children: member.name }),
1191
- member.required ? " (required)" : ""
1192
- ] }),
1193
- /* @__PURE__ */ jsxs5("dd", { children: [
1194
- /* @__PURE__ */ jsx8("code", { children: member.type }),
1195
- /* @__PURE__ */ jsx8(PropNotes, { prop: member })
1196
- ] })
1197
- ] }, member.name)) })
1198
- ] }) : null
1199
- ] }),
1247
+ /* @__PURE__ */ jsx8("td", { children: /* @__PURE__ */ jsx8(PropType, { prop }) }),
1200
1248
  /* @__PURE__ */ jsx8("td", { children: /* @__PURE__ */ jsx8("code", { children: prop.defaultValue === "undefined" ? "\u2014" : prop.defaultValue ?? "\u2014" }) }),
1201
1249
  /* @__PURE__ */ jsx8("td", { children: /* @__PURE__ */ jsx8(PropNotes, { prop }) })
1202
1250
  ] }, prop.name));
@@ -1231,6 +1279,7 @@ function PropsTable({
1231
1279
  ] }) }),
1232
1280
  /* @__PURE__ */ jsx8("tbody", { children: /* @__PURE__ */ jsx8(PropRows, { props: component.props.filter((prop) => !prop.inheritedFrom) }) })
1233
1281
  ] }) : /* @__PURE__ */ jsx8("p", { children: "No component-specific props." }),
1282
+ /* @__PURE__ */ jsx8(InheritedLinks, { component }),
1234
1283
  component.props.some((prop) => prop.inheritedFrom) ? /* @__PURE__ */ jsxs5("details", { children: [
1235
1284
  /* @__PURE__ */ jsxs5("summary", { children: [
1236
1285
  "Inherited props (",
@@ -1567,17 +1616,6 @@ function TypePage() {
1567
1616
  // src/pages/overview.tsx
1568
1617
  import { catalog, groups, stats } from "virtual:storybook-docs/catalog";
1569
1618
  import { site } from "virtual:storybook-docs/site";
1570
-
1571
- // src/base.ts
1572
- var STORYBOOK_DOCS_BASE = (() => {
1573
- const globalBase = globalThis.__STORYBOOK_DOCS_BASE__;
1574
- if (globalBase) return globalBase.replace(/\/$/, "");
1575
- const { pathname } = window.location;
1576
- return pathname === "/" || pathname.endsWith(".html") ? "" : pathname.replace(/\/$/, "");
1577
- })();
1578
- var hrefFor = (path) => `${STORYBOOK_DOCS_BASE}${path}`;
1579
-
1580
- // src/pages/overview.tsx
1581
1619
  import { Fragment as Fragment8, jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
1582
1620
  function OverviewPage() {
1583
1621
  const startHere = catalog.slice(0, 6);
@@ -6,11 +6,23 @@ export interface PropDoc {
6
6
  defaultValue: string | null;
7
7
  description: string;
8
8
  inheritedFrom?: string;
9
+ inheritedComponent?: {
10
+ name: string;
11
+ slug: string;
12
+ };
9
13
  tags?: {
10
14
  name: string;
11
15
  text: string;
12
16
  }[];
13
17
  members?: PropDoc[];
18
+ signatures?: {
19
+ type: string;
20
+ parameters: {
21
+ name: string;
22
+ type: string;
23
+ description: string;
24
+ }[];
25
+ }[];
14
26
  }
15
27
  export interface ComponentPropsDoc {
16
28
  name: string;
package/dist/styles.css CHANGED
@@ -426,8 +426,30 @@ pre.code {
426
426
  }
427
427
  code.type {
428
428
  white-space: pre-wrap;
429
+ overflow-wrap: anywhere;
430
+ display: inline-block;
431
+ max-inline-size: 100%;
432
+ }
433
+ code.signature {
434
+ display: block;
435
+ white-space: pre;
436
+ overflow-x: auto;
437
+ padding-bottom: 4px;
438
+ }
439
+ .table-scroll > .props-table {
440
+ table-layout: fixed;
441
+ min-inline-size: 640px;
442
+ }
443
+ .props-table thead th:nth-child(1) { width: 18%; }
444
+ .props-table thead th:nth-child(2) { width: 34%; }
445
+ .props-table thead th:nth-child(3) { width: 12%; }
446
+ .props-table thead th:nth-child(4) { width: 36%; }
447
+ .props-table :is(th, td) {
448
+ overflow-wrap: anywhere;
429
449
  }
430
450
  .req {
451
+ display: inline-block;
452
+ white-space: nowrap;
431
453
  margin-left: 6px;
432
454
  font-size: 10.5px;
433
455
  letter-spacing: 0.05em;
@@ -1,5 +1,5 @@
1
1
  // src/vite-plugin.ts
2
- import { readFileSync as readFileSync3, realpathSync } from "node:fs";
2
+ import { readFileSync as readFileSync3, realpathSync as realpathSync2 } from "node:fs";
3
3
  import path7 from "node:path";
4
4
 
5
5
  // src/build/catalog.ts
@@ -248,7 +248,7 @@ function globalStylesCode(engineStyles, css = []) {
248
248
  }
249
249
 
250
250
  // src/build/props.ts
251
- import { readdirSync as readdirSync2 } from "node:fs";
251
+ import { readdirSync as readdirSync2, realpathSync } from "node:fs";
252
252
  import path3 from "node:path";
253
253
  import { withCompilerOptions } from "react-docgen-typescript";
254
254
  import ts from "typescript";
@@ -319,7 +319,7 @@ function createPropsCollector() {
319
319
  cache.program = ts.createProgram(sources, compilerOptions, cache.host, cache.program);
320
320
  return cache.program;
321
321
  };
322
- const collect = (storyFile, entryName, slug) => {
322
+ const collect = (storyFile, entryName, slug, bases = []) => {
323
323
  if (storyFile) {
324
324
  const directory = path3.dirname(storyFile);
325
325
  const sources = readdirSync2(directory).filter((file) => /\.tsx?$/.test(file) && !file.endsWith(".stories.tsx")).map((file) => path3.join(directory, file));
@@ -331,7 +331,7 @@ function createPropsCollector() {
331
331
  })).filter((component) => component.props.length > 0);
332
332
  const matchesOwnName = (name) => entryName.length > 0 && name.toLowerCase().startsWith(entryName.toLowerCase());
333
333
  const relevant = parsed.some((component) => matchesOwnName(component.name)) ? parsed.filter((component) => matchesOwnName(component.name)) : parsed;
334
- const typed = collectTypeProps(sources, entryName);
334
+ const typed = collectTypeProps(sources, entryName, bases);
335
335
  return mergePropsTables(typed.length > 0 ? [...relevant.filter((table) => typed.some((item) => item.name === table.name)), ...typed] : relevant);
336
336
  } catch (cause) {
337
337
  console.warn(`storybook-docs: could not read props for "${slug}" (${String(cause)})`);
@@ -340,10 +340,45 @@ function createPropsCollector() {
340
340
  }
341
341
  return [];
342
342
  };
343
- const collectTypeProps = (sources, entryName) => {
343
+ const collectTypeProps = (sources, entryName, bases) => {
344
344
  const program = createPropsProgram(sources, TYPE_PROPS_COMPILER_OPTIONS);
345
345
  const checker = program.getTypeChecker();
346
346
  const tables = [];
347
+ const baseContracts = bases.filter((base) => base.name !== entryName).map((base) => ({
348
+ ...base,
349
+ directory: path3.dirname(realpathSync(base.storyFile)),
350
+ declarations: /* @__PURE__ */ new Set()
351
+ }));
352
+ const readContract = (type, location, declarations, depth = 0) => {
353
+ for (const signature of type.getCallSignatures()) {
354
+ const parameter = signature.getParameters()[0];
355
+ if (!parameter) continue;
356
+ const propsType = checker.getTypeOfSymbolAtLocation(parameter, signature.getDeclaration() ?? location);
357
+ for (const prop of checker.getPropertiesOfType(propsType)) {
358
+ prop.declarations?.forEach((declaration) => declarations.add(declaration));
359
+ }
360
+ }
361
+ if (depth >= 2) return;
362
+ for (const member of checker.getPropertiesOfType(type)) {
363
+ if (["bind", "call", "apply"].includes(member.getName())) continue;
364
+ const memberType = checker.getTypeOfSymbolAtLocation(member, member.valueDeclaration ?? location);
365
+ if (memberType.getCallSignatures().length) readContract(memberType, location, declarations, depth + 1);
366
+ }
367
+ };
368
+ for (const source of program.getSourceFiles()) {
369
+ if (isExternal(source.fileName)) continue;
370
+ const directory = path3.dirname(realpathSync(source.fileName));
371
+ const candidates = baseContracts.filter((base) => base.directory === directory);
372
+ if (!candidates.length) continue;
373
+ const module = checker.getSymbolAtLocation(source);
374
+ if (!module) continue;
375
+ for (const symbol of checker.getExportsOfModule(module)) {
376
+ for (const base of candidates.filter((candidate) => candidate.name === symbol.getName())) {
377
+ const target = symbol.flags & ts.SymbolFlags.Alias ? checker.getAliasedSymbol(symbol) : symbol;
378
+ readContract(checker.getTypeOfSymbolAtLocation(target, target.valueDeclaration ?? source), source, base.declarations);
379
+ }
380
+ }
381
+ }
347
382
  const unresolved = /* @__PURE__ */ Symbol("unresolved");
348
383
  const literalValue = (node, seen = /* @__PURE__ */ new Set()) => {
349
384
  if (seen.has(node)) return unresolved;
@@ -391,9 +426,26 @@ function createPropsCollector() {
391
426
  const printedType = checker.typeToString(propType, void 0, typeFlags);
392
427
  const displayType = literalUnion && propType.isUnion() ? propType.types.filter((part) => !optional || !(part.flags & ts.TypeFlags.Undefined)).map((part) => checker.typeToString(part, void 0, typeFlags)).join(" | ").replace(/false \| true/g, "boolean") : optional ? printedType.replace(/ \| undefined$/, "") : printedType;
393
428
  const parent = declaration?.parent;
394
- const inheritedFrom = declaration && (isExternal(declaration.getSourceFile().fileName) || !sources.includes(declaration.getSourceFile().fileName)) ? parent && (ts.isInterfaceDeclaration(parent) || ts.isTypeAliasDeclaration(parent)) ? parent.name.text : path3.basename(declaration.getSourceFile().fileName) : void 0;
429
+ const declarationName = declaration ? parent && (ts.isInterfaceDeclaration(parent) || ts.isTypeAliasDeclaration(parent)) ? parent.name.text : path3.basename(declaration.getSourceFile().fileName) : void 0;
430
+ const inheritedComponent = declaration ? baseContracts.find(
431
+ (base) => base.declarations.has(declaration) && base.directory === path3.dirname(realpathSync(declaration.getSourceFile().fileName))
432
+ ) : void 0;
433
+ const inheritedFrom = declaration && (inheritedComponent || isExternal(declaration.getSourceFile().fileName) || !sources.includes(declaration.getSourceFile().fileName)) ? declarationName : void 0;
395
434
  const memberType = checker.getNonNullableType(propType);
396
- const hasLocalMembers = !checker.isArrayType(memberType) && !checker.isTupleType(memberType) && !memberType.getCallSignatures().length && checker.getPropertiesOfType(memberType).some((member) => member.declarations?.some((node) => !isExternal(node.getSourceFile().fileName)));
435
+ const nonCallableAlternatives = propType.isUnion() ? propType.types.filter((part) => Boolean(part.flags & (ts.TypeFlags.Null | ts.TypeFlags.Undefined)) && (!optional || !(part.flags & ts.TypeFlags.Undefined))).map((part) => checker.typeToString(part, void 0, typeFlags)) : [];
436
+ const signatures = memberType.getCallSignatures().map((signature) => {
437
+ const signatureType = checker.signatureToString(signature, location, typeFlags | ts.TypeFormatFlags.WriteArrowStyleSignature);
438
+ return {
439
+ type: nonCallableAlternatives.length ? `(${signatureType}) | ${nonCallableAlternatives.join(" | ")}` : signatureType,
440
+ parameters: signature.getParameters().map((parameter) => ({
441
+ name: parameter.getName(),
442
+ type: checker.typeToString(checker.getTypeOfSymbolAtLocation(parameter, parameter.valueDeclaration ?? location), location, typeFlags),
443
+ description: ts.displayPartsToString(parameter.getDocumentationComment(checker)).trim()
444
+ }))
445
+ };
446
+ });
447
+ const objectTypes = memberType.isUnion() ? memberType.types : [memberType];
448
+ const hasLocalMembers = objectTypes.every((part) => Boolean(part.flags & (ts.TypeFlags.Object | ts.TypeFlags.Intersection))) && !checker.isArrayType(memberType) && !checker.isTupleType(memberType) && !signatures.length && checker.getPropertiesOfType(memberType).some((member) => member.declarations?.some((node) => !isExternal(node.getSourceFile().fileName)));
397
449
  return {
398
450
  name: symbol.getName(),
399
451
  type: displayType || "unknown",
@@ -402,6 +454,8 @@ function createPropsCollector() {
402
454
  defaultValue: null,
403
455
  description: ts.displayPartsToString(symbol.getDocumentationComment(checker)).trim(),
404
456
  inheritedFrom,
457
+ inheritedComponent: inheritedComponent ? { name: inheritedComponent.name, slug: inheritedComponent.slug } : void 0,
458
+ signatures: signatures.length ? signatures : void 0,
405
459
  tags: symbol.getJsDocTags(checker).filter((tag) => ["deprecated", "since", "example"].includes(tag.name)).map((tag) => ({ name: tag.name, text: ts.displayPartsToString(tag.text) })),
406
460
  members: depth === 0 && (symbol.getName() === "slotProps" || !inheritedFrom && hasLocalMembers) ? describeProps(memberType, location, depth + 1) : void 0
407
461
  };
@@ -928,7 +982,10 @@ function storybookDocs(options2) {
928
982
  let components = propsCache.get(slug);
929
983
  if (!components) {
930
984
  const name = catalog.entries.find((entry) => entry.slug === slug)?.name ?? "";
931
- components = propsCollector.collect(catalog.files.get(slug), name, slug);
985
+ components = propsCollector.collect(catalog.files.get(slug), name, slug, catalog.entries.flatMap((entry) => {
986
+ const storyFile = catalog.files.get(entry.slug);
987
+ return storyFile ? [{ name: entry.name, slug: entry.slug, storyFile }] : [];
988
+ }));
932
989
  propsCache.set(slug, components);
933
990
  }
934
991
  return propsCode(components);
@@ -980,7 +1037,7 @@ export const storySource = ${JSON.stringify(config.stories.from)};`,
980
1037
  },
981
1038
  resolveId(id) {
982
1039
  if (id.startsWith(VIRTUAL_PREFIX)) return `\0${id}`;
983
- if (id.startsWith(FILE_PREFIX)) return realpathSync(id.slice(FILE_PREFIX.length));
1040
+ if (id.startsWith(FILE_PREFIX)) return realpathSync2(id.slice(FILE_PREFIX.length));
984
1041
  if (id === "storybook/test") return `\0${VIRTUAL_PREFIX}stub-storybook-test`;
985
1042
  return null;
986
1043
  },
@@ -993,7 +1050,7 @@ export const storySource = ${JSON.stringify(config.stories.from)};`,
993
1050
  if (name.startsWith("story-sources/")) {
994
1051
  const storyFile = catalog.files.get(name.slice("story-sources/".length));
995
1052
  if (!storyFile) return storySourcesCode({});
996
- const file = realpathSync(storyFile);
1053
+ const file = realpathSync2(storyFile);
997
1054
  let sources = storySourcesCache.get(file);
998
1055
  if (!sources) {
999
1056
  sources = extractStorySources(readFileSync3(file, "utf8"));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jfrader/storybook-docs",
3
- "version": "0.9.2",
3
+ "version": "0.10.0",
4
4
  "type": "module",
5
5
  "description": "A documentation front for any design system that has Storybook: navigation, live examples and API tables are generated from the story files themselves.",
6
6
  "exports": {