From ee243bf206b5b59f4bad71c10529c0511696eafe Mon Sep 17 00:00:00 2001 From: "bit-sync[bot]" Date: Wed, 5 Aug 2026 13:03:46 +0000 Subject: [PATCH] chore(bit-sync): sync git to latest main scope versions (122 file(s)) [bit-sync] --- .bitmap | 294 +++++++++++++++ .../api-reference/api-compare.module.scss | 8 + .../api-reference/api-compare.section.tsx | 33 ++ components/api-reference/api-compare.tsx | 84 +++++ .../api-reference/api-reference.aspect.ts | 7 + .../api-reference.ui.runtime.tsx | 102 ++++++ components/api-reference/esm.mjs | 7 + components/api-reference/index.ts | 7 + .../api-reference-explorer.module.scss | 8 + .../api-reference-explorer.tsx | 33 ++ .../explorer/api-reference-explorer/index.ts | 1 + components/hooks/use-api-ref-url/index.ts | 1 + .../hooks/use-api-ref-url/use-api-ref-url.ts | 24 ++ components/hooks/use-api-renderers/index.ts | 2 + .../use-api-renderers/use-api-renderers.tsx | 30 ++ components/hooks/use-api/index.ts | 1 + components/hooks/use-api/use-api.ts | 36 ++ components/hooks/use-schema/index.ts | 1 + components/hooks/use-schema/use-schema.ts | 36 ++ .../api-node-renderer/api-node-renderer.ts | 20 ++ components/models/api-node-renderer/index.ts | 4 + .../models/api-node-renderer/node.module.scss | 13 + .../api-reference-model.ts | 176 +++++++++ .../models/api-reference-model/index.ts | 1 + ...pi-reference.table-of-contents.module.scss | 80 +++++ .../api-reference.table-of-contents.tsx | 54 +++ .../api-reference-table-of-contents/index.ts | 2 + ...-schema-nodes-overview-summary.module.scss | 182 ++++++++++ .../grouped-schema-nodes-overview-summary.tsx | 259 ++++++++++++++ .../index.ts | 2 + .../api-node-details.module.scss | 96 +++++ .../api-node-details/api-node-details.tsx | 338 ++++++++++++++++++ .../extract-code-block.spec.ts | 21 ++ .../api-node-details/extract-code-block.ts | 23 ++ .../renderers/api-node-details/index.ts | 2 + .../class/class.renderer.module.scss | 17 + components/renderers/class/class.renderer.tsx | 85 +++++ components/renderers/class/index.ts | 1 + .../decorator/decorator.renderer.module.scss | 10 + .../decorator/decorator.renderer.tsx | 55 +++ components/renderers/decorator/index.ts | 1 + .../renderers/default-node-renderers/index.ts | 60 ++++ .../renderers/enum/enum.renderer.module.scss | 0 components/renderers/enum/enum.renderer.tsx | 50 +++ components/renderers/enum/index.ts | 1 + .../function/function.renderer.module.scss | 58 +++ .../renderers/function/function.renderer.tsx | 102 ++++++ components/renderers/function/index.ts | 1 + .../grouped-schema-nodes-summary.module.scss | 57 +++ .../grouped-schema-nodes-summary.tsx | 152 ++++++++ .../grouped-schema-nodes-summary/index.ts | 1 + components/renderers/inference-type/index.ts | 1 + .../inference-type/inference-type.module.scss | 37 ++ .../inference-type.renderer.tsx | 95 +++++ components/renderers/interface/index.ts | 1 + .../interface/interface.renderer.module.scss | 0 .../interface/interface.renderer.tsx | 53 +++ components/renderers/parameter/index.ts | 1 + .../parameter/parameter.renderer.module.scss | 58 +++ .../parameter/parameter.renderer.tsx | 157 ++++++++ components/renderers/react/index.ts | 1 + .../react/react.renderer.module.scss | 98 +++++ components/renderers/react/react.renderer.tsx | 205 +++++++++++ .../enum-member-summary.module.scss | 16 + .../enum-member-summary.tsx | 59 +++ .../function-node-summary.module.scss | 66 ++++ .../function-node-summary.tsx | 138 +++++++ .../schema-node-member-summary/index.ts | 4 + .../variable-node-summary.module.scss | 20 ++ .../variable-node-summary.tsx | 82 +++++ .../renderers/schema-nodes-index/index.ts | 1 + .../schema-nodes-index.module.scss | 56 +++ .../schema-nodes-index/schema-nodes-index.tsx | 156 ++++++++ components/renderers/this/index.ts | 1 + components/renderers/this/this.renderer.tsx | 28 ++ components/renderers/tuple-type/index.ts | 1 + .../tuple-type.renderer.module.scss | 25 ++ .../tuple-type/tuple-type.renderer.tsx | 69 ++++ components/renderers/type-array/index.ts | 1 + .../type-array.renderer.module.scss | 5 + .../type-array/type-array.renderer.tsx | 42 +++ .../renderers/type-intersection/index.ts | 1 + .../type-intersection.renderer.module.scss | 21 ++ .../type-intersection.renderer.tsx | 63 ++++ components/renderers/type-literal/index.ts | 1 + .../type-literal.renderer.module.scss | 14 + .../type-literal/type-literal.renderer.tsx | 62 ++++ components/renderers/type-ref/index.ts | 1 + .../type-ref/type-ref.renderer.module.scss | 47 +++ .../renderers/type-ref/type-ref.renderer.tsx | 205 +++++++++++ components/renderers/type-union/index.ts | 1 + .../type-union.renderer.module.scss | 11 + .../type-union/type-union.renderer.tsx | 60 ++++ components/renderers/type/index.ts | 1 + .../renderers/type/type.renderer.module.scss | 0 components/renderers/type/type.renderer.tsx | 63 ++++ components/renderers/unresolved/index.ts | 1 + .../unresolved.renderer.module.scss | 0 .../unresolved/unresolved.renderer.tsx | 16 + components/renderers/variable/index.ts | 1 + .../variable/variable.renderer.module.scss | 0 .../renderers/variable/variable.renderer.tsx | 32 ++ .../api-reference-page.module.scss | 105 ++++++ .../api-reference-page/api-reference-page.tsx | 171 +++++++++ .../sections/api-reference-page/index.ts | 1 + .../api-reference.section.tsx | 18 + .../sections/api-reference-section/index.ts | 1 + components/tagged-exports/index.ts | 2 + .../tagged-exports/tagged-exports.module.scss | 72 ++++ components/tagged-exports/tagged-exports.tsx | 98 +++++ .../code-editor-options.ts | 26 ++ components/utils/code-editor-options/index.ts | 1 + .../copy-schema-node/copy-schema-node.ts | 15 + components/utils/copy-schema-node/index.ts | 1 + .../custom-theme.js | 198 ++++++++++ .../index.ts | 4 + .../group-schema-node-by-signature.ts | 47 +++ .../group-schema-node-by-signature/index.ts | 1 + .../schema-node-signature-transform/index.ts | 1 + .../schema-node-signature.transform.ts | 12 + components/utils/sort-api-nodes/index.ts | 1 + .../utils/sort-api-nodes/sort-api-nodes.ts | 19 + 122 files changed, 5511 insertions(+) create mode 100644 components/api-reference/api-compare.module.scss create mode 100644 components/api-reference/api-compare.section.tsx create mode 100644 components/api-reference/api-compare.tsx create mode 100644 components/api-reference/api-reference.aspect.ts create mode 100644 components/api-reference/api-reference.ui.runtime.tsx create mode 100644 components/api-reference/esm.mjs create mode 100644 components/api-reference/index.ts create mode 100644 components/explorer/api-reference-explorer/api-reference-explorer.module.scss create mode 100644 components/explorer/api-reference-explorer/api-reference-explorer.tsx create mode 100644 components/explorer/api-reference-explorer/index.ts create mode 100644 components/hooks/use-api-ref-url/index.ts create mode 100644 components/hooks/use-api-ref-url/use-api-ref-url.ts create mode 100644 components/hooks/use-api-renderers/index.ts create mode 100644 components/hooks/use-api-renderers/use-api-renderers.tsx create mode 100644 components/hooks/use-api/index.ts create mode 100644 components/hooks/use-api/use-api.ts create mode 100644 components/hooks/use-schema/index.ts create mode 100644 components/hooks/use-schema/use-schema.ts create mode 100644 components/models/api-node-renderer/api-node-renderer.ts create mode 100644 components/models/api-node-renderer/index.ts create mode 100644 components/models/api-node-renderer/node.module.scss create mode 100644 components/models/api-reference-model/api-reference-model.ts create mode 100644 components/models/api-reference-model/index.ts create mode 100644 components/overview/api-reference-table-of-contents/api-reference.table-of-contents.module.scss create mode 100644 components/overview/api-reference-table-of-contents/api-reference.table-of-contents.tsx create mode 100644 components/overview/api-reference-table-of-contents/index.ts create mode 100644 components/overview/renderers/grouped-schema-nodes-overview-summary/grouped-schema-nodes-overview-summary.module.scss create mode 100644 components/overview/renderers/grouped-schema-nodes-overview-summary/grouped-schema-nodes-overview-summary.tsx create mode 100644 components/overview/renderers/grouped-schema-nodes-overview-summary/index.ts create mode 100644 components/renderers/api-node-details/api-node-details.module.scss create mode 100644 components/renderers/api-node-details/api-node-details.tsx create mode 100644 components/renderers/api-node-details/extract-code-block.spec.ts create mode 100644 components/renderers/api-node-details/extract-code-block.ts create mode 100644 components/renderers/api-node-details/index.ts create mode 100644 components/renderers/class/class.renderer.module.scss create mode 100644 components/renderers/class/class.renderer.tsx create mode 100644 components/renderers/class/index.ts create mode 100644 components/renderers/decorator/decorator.renderer.module.scss create mode 100644 components/renderers/decorator/decorator.renderer.tsx create mode 100644 components/renderers/decorator/index.ts create mode 100644 components/renderers/default-node-renderers/index.ts create mode 100644 components/renderers/enum/enum.renderer.module.scss create mode 100644 components/renderers/enum/enum.renderer.tsx create mode 100644 components/renderers/enum/index.ts create mode 100644 components/renderers/function/function.renderer.module.scss create mode 100644 components/renderers/function/function.renderer.tsx create mode 100644 components/renderers/function/index.ts create mode 100644 components/renderers/grouped-schema-nodes-summary/grouped-schema-nodes-summary.module.scss create mode 100644 components/renderers/grouped-schema-nodes-summary/grouped-schema-nodes-summary.tsx create mode 100644 components/renderers/grouped-schema-nodes-summary/index.ts create mode 100644 components/renderers/inference-type/index.ts create mode 100644 components/renderers/inference-type/inference-type.module.scss create mode 100644 components/renderers/inference-type/inference-type.renderer.tsx create mode 100644 components/renderers/interface/index.ts create mode 100644 components/renderers/interface/interface.renderer.module.scss create mode 100644 components/renderers/interface/interface.renderer.tsx create mode 100644 components/renderers/parameter/index.ts create mode 100644 components/renderers/parameter/parameter.renderer.module.scss create mode 100644 components/renderers/parameter/parameter.renderer.tsx create mode 100644 components/renderers/react/index.ts create mode 100644 components/renderers/react/react.renderer.module.scss create mode 100644 components/renderers/react/react.renderer.tsx create mode 100644 components/renderers/schema-node-member-summary/enum-member-summary.module.scss create mode 100644 components/renderers/schema-node-member-summary/enum-member-summary.tsx create mode 100644 components/renderers/schema-node-member-summary/function-node-summary.module.scss create mode 100644 components/renderers/schema-node-member-summary/function-node-summary.tsx create mode 100644 components/renderers/schema-node-member-summary/index.ts create mode 100644 components/renderers/schema-node-member-summary/variable-node-summary.module.scss create mode 100644 components/renderers/schema-node-member-summary/variable-node-summary.tsx create mode 100644 components/renderers/schema-nodes-index/index.ts create mode 100644 components/renderers/schema-nodes-index/schema-nodes-index.module.scss create mode 100644 components/renderers/schema-nodes-index/schema-nodes-index.tsx create mode 100644 components/renderers/this/index.ts create mode 100644 components/renderers/this/this.renderer.tsx create mode 100644 components/renderers/tuple-type/index.ts create mode 100644 components/renderers/tuple-type/tuple-type.renderer.module.scss create mode 100644 components/renderers/tuple-type/tuple-type.renderer.tsx create mode 100644 components/renderers/type-array/index.ts create mode 100644 components/renderers/type-array/type-array.renderer.module.scss create mode 100644 components/renderers/type-array/type-array.renderer.tsx create mode 100644 components/renderers/type-intersection/index.ts create mode 100644 components/renderers/type-intersection/type-intersection.renderer.module.scss create mode 100644 components/renderers/type-intersection/type-intersection.renderer.tsx create mode 100644 components/renderers/type-literal/index.ts create mode 100644 components/renderers/type-literal/type-literal.renderer.module.scss create mode 100644 components/renderers/type-literal/type-literal.renderer.tsx create mode 100644 components/renderers/type-ref/index.ts create mode 100644 components/renderers/type-ref/type-ref.renderer.module.scss create mode 100644 components/renderers/type-ref/type-ref.renderer.tsx create mode 100644 components/renderers/type-union/index.ts create mode 100644 components/renderers/type-union/type-union.renderer.module.scss create mode 100644 components/renderers/type-union/type-union.renderer.tsx create mode 100644 components/renderers/type/index.ts create mode 100644 components/renderers/type/type.renderer.module.scss create mode 100644 components/renderers/type/type.renderer.tsx create mode 100644 components/renderers/unresolved/index.ts create mode 100644 components/renderers/unresolved/unresolved.renderer.module.scss create mode 100644 components/renderers/unresolved/unresolved.renderer.tsx create mode 100644 components/renderers/variable/index.ts create mode 100644 components/renderers/variable/variable.renderer.module.scss create mode 100644 components/renderers/variable/variable.renderer.tsx create mode 100644 components/sections/api-reference-page/api-reference-page.module.scss create mode 100644 components/sections/api-reference-page/api-reference-page.tsx create mode 100644 components/sections/api-reference-page/index.ts create mode 100644 components/sections/api-reference-section/api-reference.section.tsx create mode 100644 components/sections/api-reference-section/index.ts create mode 100644 components/tagged-exports/index.ts create mode 100644 components/tagged-exports/tagged-exports.module.scss create mode 100644 components/tagged-exports/tagged-exports.tsx create mode 100644 components/utils/code-editor-options/code-editor-options.ts create mode 100644 components/utils/code-editor-options/index.ts create mode 100644 components/utils/copy-schema-node/copy-schema-node.ts create mode 100644 components/utils/copy-schema-node/index.ts create mode 100644 components/utils/custom-prism-syntax-highlighter-theme/custom-theme.js create mode 100644 components/utils/custom-prism-syntax-highlighter-theme/index.ts create mode 100644 components/utils/group-schema-node-by-signature/group-schema-node-by-signature.ts create mode 100644 components/utils/group-schema-node-by-signature/index.ts create mode 100644 components/utils/schema-node-signature-transform/index.ts create mode 100644 components/utils/schema-node-signature-transform/schema-node-signature.transform.ts create mode 100644 components/utils/sort-api-nodes/index.ts create mode 100644 components/utils/sort-api-nodes/sort-api-nodes.ts diff --git a/.bitmap b/.bitmap index 74fdbc1..1b9d16c 100644 --- a/.bitmap +++ b/.bitmap @@ -9,6 +9,20 @@ */ { + "api-reference": { + "name": "api-reference", + "scope": "teambit.api-reference", + "version": "1.0.1095", + "mainFile": "index.ts", + "rootDir": "components/api-reference" + }, + "explorer/api-reference-explorer": { + "name": "explorer/api-reference-explorer", + "scope": "teambit.api-reference", + "version": "0.0.39", + "mainFile": "index.ts", + "rootDir": "components/explorer/api-reference-explorer" + }, "greeting": { "name": "greeting", "scope": "", @@ -17,5 +31,285 @@ "mainFile": "index.ts", "rootDir": "components/greeting" }, + "hooks/use-api": { + "name": "hooks/use-api", + "scope": "teambit.api-reference", + "version": "0.0.57", + "mainFile": "index.ts", + "rootDir": "components/hooks/use-api" + }, + "hooks/use-api-ref-url": { + "name": "hooks/use-api-ref-url", + "scope": "teambit.api-reference", + "version": "0.0.25", + "mainFile": "index.ts", + "rootDir": "components/hooks/use-api-ref-url" + }, + "hooks/use-api-renderers": { + "name": "hooks/use-api-renderers", + "scope": "teambit.api-reference", + "version": "0.0.37", + "mainFile": "index.ts", + "rootDir": "components/hooks/use-api-renderers" + }, + "hooks/use-schema": { + "name": "hooks/use-schema", + "scope": "teambit.api-reference", + "version": "0.0.35", + "mainFile": "index.ts", + "rootDir": "components/hooks/use-schema" + }, + "models/api-node-renderer": { + "name": "models/api-node-renderer", + "scope": "teambit.api-reference", + "version": "0.0.53", + "mainFile": "index.ts", + "rootDir": "components/models/api-node-renderer" + }, + "models/api-reference-model": { + "name": "models/api-reference-model", + "scope": "teambit.api-reference", + "version": "0.0.53", + "mainFile": "index.ts", + "rootDir": "components/models/api-reference-model" + }, + "overview/api-reference-table-of-contents": { + "name": "overview/api-reference-table-of-contents", + "scope": "teambit.api-reference", + "version": "0.0.41", + "mainFile": "index.ts", + "rootDir": "components/overview/api-reference-table-of-contents" + }, + "overview/renderers/grouped-schema-nodes-overview-summary": { + "name": "overview/renderers/grouped-schema-nodes-overview-summary", + "scope": "teambit.api-reference", + "version": "0.0.69", + "mainFile": "index.ts", + "rootDir": "components/overview/renderers/grouped-schema-nodes-overview-summary" + }, + "renderers/api-node-details": { + "name": "renderers/api-node-details", + "scope": "teambit.api-reference", + "version": "0.0.84", + "mainFile": "index.ts", + "rootDir": "components/renderers/api-node-details" + }, + "renderers/class": { + "name": "renderers/class", + "scope": "teambit.api-reference", + "version": "0.0.86", + "mainFile": "index.ts", + "rootDir": "components/renderers/class" + }, + "renderers/decorator": { + "name": "renderers/decorator", + "scope": "teambit.api-reference", + "version": "0.0.40", + "mainFile": "index.ts", + "rootDir": "components/renderers/decorator" + }, + "renderers/default-node-renderers": { + "name": "renderers/default-node-renderers", + "scope": "teambit.api-reference", + "version": "0.0.22", + "mainFile": "index.ts", + "rootDir": "components/renderers/default-node-renderers" + }, + "renderers/enum": { + "name": "renderers/enum", + "scope": "teambit.api-reference", + "version": "0.0.84", + "mainFile": "index.ts", + "rootDir": "components/renderers/enum" + }, + "renderers/function": { + "name": "renderers/function", + "scope": "teambit.api-reference", + "version": "0.0.88", + "mainFile": "index.ts", + "rootDir": "components/renderers/function" + }, + "renderers/grouped-schema-nodes-summary": { + "name": "renderers/grouped-schema-nodes-summary", + "scope": "teambit.api-reference", + "version": "0.0.82", + "mainFile": "index.ts", + "rootDir": "components/renderers/grouped-schema-nodes-summary" + }, + "renderers/inference-type": { + "name": "renderers/inference-type", + "scope": "teambit.api-reference", + "version": "0.0.66", + "mainFile": "index.ts", + "rootDir": "components/renderers/inference-type" + }, + "renderers/interface": { + "name": "renderers/interface", + "scope": "teambit.api-reference", + "version": "0.0.84", + "mainFile": "index.ts", + "rootDir": "components/renderers/interface" + }, + "renderers/parameter": { + "name": "renderers/parameter", + "scope": "teambit.api-reference", + "version": "0.0.69", + "mainFile": "index.ts", + "rootDir": "components/renderers/parameter" + }, + "renderers/react": { + "name": "renderers/react", + "scope": "teambit.api-reference", + "version": "0.0.69", + "mainFile": "index.ts", + "rootDir": "components/renderers/react" + }, + "renderers/schema-node-member-summary": { + "name": "renderers/schema-node-member-summary", + "scope": "teambit.api-reference", + "version": "0.0.80", + "mainFile": "index.ts", + "rootDir": "components/renderers/schema-node-member-summary" + }, + "renderers/schema-nodes-index": { + "name": "renderers/schema-nodes-index", + "scope": "teambit.api-reference", + "version": "0.0.72", + "mainFile": "index.ts", + "rootDir": "components/renderers/schema-nodes-index" + }, + "renderers/this": { + "name": "renderers/this", + "scope": "teambit.api-reference", + "version": "0.0.71", + "mainFile": "index.ts", + "rootDir": "components/renderers/this" + }, + "renderers/tuple-type": { + "name": "renderers/tuple-type", + "scope": "teambit.api-reference", + "version": "0.0.33", + "mainFile": "index.ts", + "rootDir": "components/renderers/tuple-type" + }, + "renderers/type": { + "name": "renderers/type", + "scope": "teambit.api-reference", + "version": "0.0.82", + "mainFile": "index.ts", + "rootDir": "components/renderers/type" + }, + "renderers/type-array": { + "name": "renderers/type-array", + "scope": "teambit.api-reference", + "version": "0.0.55", + "mainFile": "index.ts", + "rootDir": "components/renderers/type-array" + }, + "renderers/type-intersection": { + "name": "renderers/type-intersection", + "scope": "teambit.api-reference", + "version": "0.0.55", + "mainFile": "index.ts", + "rootDir": "components/renderers/type-intersection" + }, + "renderers/type-literal": { + "name": "renderers/type-literal", + "scope": "teambit.api-reference", + "version": "0.0.75", + "mainFile": "index.ts", + "rootDir": "components/renderers/type-literal" + }, + "renderers/type-ref": { + "name": "renderers/type-ref", + "scope": "teambit.api-reference", + "version": "0.0.101", + "mainFile": "index.ts", + "rootDir": "components/renderers/type-ref" + }, + "renderers/type-union": { + "name": "renderers/type-union", + "scope": "teambit.api-reference", + "version": "0.0.55", + "mainFile": "index.ts", + "rootDir": "components/renderers/type-union" + }, + "renderers/unresolved": { + "name": "renderers/unresolved", + "scope": "teambit.api-reference", + "version": "0.0.82", + "mainFile": "index.ts", + "rootDir": "components/renderers/unresolved" + }, + "renderers/variable": { + "name": "renderers/variable", + "scope": "teambit.api-reference", + "version": "0.0.82", + "mainFile": "index.ts", + "rootDir": "components/renderers/variable" + }, + "sections/api-reference-page": { + "name": "sections/api-reference-page", + "scope": "teambit.api-reference", + "version": "0.0.120", + "mainFile": "index.ts", + "rootDir": "components/sections/api-reference-page" + }, + "sections/api-reference-section": { + "name": "sections/api-reference-section", + "scope": "teambit.api-reference", + "version": "0.0.16", + "mainFile": "index.ts", + "rootDir": "components/sections/api-reference-section" + }, + "tagged-exports": { + "name": "tagged-exports", + "scope": "teambit.api-reference", + "version": "0.0.53", + "mainFile": "index.ts", + "rootDir": "components/tagged-exports" + }, + "utils/code-editor-options": { + "name": "utils/code-editor-options", + "scope": "teambit.api-reference", + "version": "0.0.16", + "mainFile": "index.ts", + "rootDir": "components/utils/code-editor-options" + }, + "utils/copy-schema-node": { + "name": "utils/copy-schema-node", + "scope": "teambit.api-reference", + "version": "0.0.45", + "mainFile": "index.ts", + "rootDir": "components/utils/copy-schema-node" + }, + "utils/custom-prism-syntax-highlighter-theme": { + "name": "utils/custom-prism-syntax-highlighter-theme", + "scope": "teambit.api-reference", + "version": "0.0.16", + "mainFile": "index.ts", + "rootDir": "components/utils/custom-prism-syntax-highlighter-theme" + }, + "utils/group-schema-node-by-signature": { + "name": "utils/group-schema-node-by-signature", + "scope": "teambit.api-reference", + "version": "0.0.45", + "mainFile": "index.ts", + "rootDir": "components/utils/group-schema-node-by-signature" + }, + "utils/schema-node-signature-transform": { + "name": "utils/schema-node-signature-transform", + "scope": "teambit.api-reference", + "version": "0.0.47", + "mainFile": "index.ts", + "rootDir": "components/utils/schema-node-signature-transform" + }, + "utils/sort-api-nodes": { + "name": "utils/sort-api-nodes", + "scope": "teambit.api-reference", + "version": "0.0.53", + "mainFile": "index.ts", + "rootDir": "components/utils/sort-api-nodes" + }, "$schema-version": "17.0.0" } \ No newline at end of file diff --git a/components/api-reference/api-compare.module.scss b/components/api-reference/api-compare.module.scss new file mode 100644 index 0000000..f1e648e --- /dev/null +++ b/components/api-reference/api-compare.module.scss @@ -0,0 +1,8 @@ +.apiCompareContainer { + display: flex; + flex-direction: column; + height: 100%; + overflow: auto; + padding: 16px 24px; + max-width: 980px; +} diff --git a/components/api-reference/api-compare.section.tsx b/components/api-reference/api-compare.section.tsx new file mode 100644 index 0000000..0ced91d --- /dev/null +++ b/components/api-reference/api-compare.section.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import type { Section } from '@teambit/component'; +import type { TabItem } from '@teambit/component.ui.component-compare.models.component-compare-props'; +import type { ChangeType } from '@teambit/component.ui.component-compare.models.component-compare-change-type'; +import type { ApiDiffInsight } from '@teambit/semantics.ui.api-diff-view'; +import { APICompare } from './api-compare'; + +// Use string cast since the external ChangeType enum doesn't have API yet +const ChangeTypeAPI = 'API' as unknown as ChangeType; + +export class APICompareSection implements TabItem, Section { + route: { path: string; element: React.ReactNode }; + + constructor(getInsights?: () => ApiDiffInsight[]) { + // built in the constructor (not a field initializer) so the element captures the + // getter regardless of class-field initialization order. + this.route = { + path: 'api/*', + element: , + }; + } + + navigationLink = { + href: 'api', + children: 'API', + }; + + props = this.navigationLink; + + order = 15; + changeType = ChangeTypeAPI; + id = 'api'; +} diff --git a/components/api-reference/api-compare.tsx b/components/api-reference/api-compare.tsx new file mode 100644 index 0000000..6d15e47 --- /dev/null +++ b/components/api-reference/api-compare.tsx @@ -0,0 +1,84 @@ +import React from 'react'; +import { useComponentCompare } from '@teambit/component.ui.component-compare.context'; +import { + ComponentApiDiffSection, + ApiDiffSlimRow, + ApiDiffInsightProvider, + useApiDiff, +} from '@teambit/semantics.ui.api-diff-view'; +import type { ApiDiffInsight } from '@teambit/semantics.ui.api-diff-view'; +import styles from './api-compare.module.scss'; + +// single source of truth for the API diff model lives in api-diff-view. +export type { APIDiffResult, APIDiffChange, APIDiffDetail } from '@teambit/semantics.ui.api-diff-view'; + +export type APICompareProps = { + /** resolved lazily at render time — slot registrations can land after the section is constructed */ + getInsights?: () => ApiDiffInsight[]; +}; + +/** + * the API tab of the single-component compare page. thin host around the same + * `ComponentApiDiffSection` the lane-compare API view uses — one implementation, two hosts. + * data comes from the compare context (fetched once by ComponentCompare). + */ +export function APICompare({ getInsights }: APICompareProps) { + const compareContext = useComponentCompare(); + const baseModel = compareContext?.base?.model; + const compareModel = compareContext?.compare?.model; + + // Prefer a diff already on the context — the legacy `ComponentCompare` fetches it once and sets + // it there. The redesign single-component page does NOT put it on its inline context, so we fetch + // on our own. This hook is only mounted when the API view is the active tab (the page renders the + // element on-demand), so lane-compare — which renders one panel per component and never mounts + // this tab — pays nothing. + const contextApiDiff = compareContext?.apiDiffResult; + const shouldSelfFetch = contextApiDiff === undefined; + const { result: fetchedApiDiff } = useApiDiff(baseModel?.id.toString(), compareModel?.id.toString(), { + skip: !shouldSelfFetch, + }); + const apiDiffResult = shouldSelfFetch ? fetchedApiDiff : contextApiDiff; + + // memoized so the insights context value keeps a stable identity — otherwise a fresh array every + // render would re-render every `useApiDiffInsights` consumer (incl. the React.memo'd ApiChangeBlock). + const insights = React.useMemo(() => getInsights?.(), [getInsights]); + + if (!compareContext) return null; + + const id = (compareModel || baseModel)?.id; + if (!id) return null; + + const componentIdStr = id.toStringWithoutVersion(); + const baseVersion = baseModel?.id.version; + const compareVersion = compareModel?.id.version; + + // no base (first version) or identical versions — nothing to diff, not a failure. + const nothingToCompare = apiDiffResult === null && (!baseVersion || baseVersion === compareVersion); + + return ( + +
+ {nothingToCompare ? ( + + ) : ( + + )} +
+
+ ); +} diff --git a/components/api-reference/api-reference.aspect.ts b/components/api-reference/api-reference.aspect.ts new file mode 100644 index 0000000..7240845 --- /dev/null +++ b/components/api-reference/api-reference.aspect.ts @@ -0,0 +1,7 @@ +import { Aspect } from '@teambit/harmony'; + +export const APIReferenceAspect = Aspect.create({ + id: 'teambit.api-reference/api-reference', +}); + +export default APIReferenceAspect; diff --git a/components/api-reference/api-reference.ui.runtime.tsx b/components/api-reference/api-reference.ui.runtime.tsx new file mode 100644 index 0000000..53a6855 --- /dev/null +++ b/components/api-reference/api-reference.ui.runtime.tsx @@ -0,0 +1,102 @@ +import React from 'react'; +import { flatten } from 'lodash'; +import type { ComponentUI } from '@teambit/component'; +import { ComponentAspect } from '@teambit/component'; +import { UIRuntime } from '@teambit/ui'; +import { APIRefPage } from '@teambit/api-reference.sections.api-reference-page'; +import { APIRefSection } from '@teambit/api-reference.sections.api-reference-section'; +import type { Harmony, SlotRegistry } from '@teambit/harmony'; +import { Slot } from '@teambit/harmony'; +import type { APINodeRenderer } from '@teambit/api-reference.models.api-node-renderer'; +import { defaultNodeRenderers } from '@teambit/api-reference.renderers.default-node-renderers'; +import { APIRefRenderersProvider } from '@teambit/api-reference.hooks.use-api-renderers'; +import type { SchemaNodeConstructor } from '@teambit/semantics.entities.semantic-schema'; +import { SchemaRegistry, Schemas } from '@teambit/semantics.entities.semantic-schema'; +import type { CodeUI } from '@teambit/code'; +import { CodeAspect } from '@teambit/code'; +import { TaggedExports } from '@teambit/tagged-exports'; +import type { WorkspaceUI } from '@teambit/workspace'; +import { WorkspaceAspect } from '@teambit/workspace'; +import type { ComponentCompareUI } from '@teambit/component-compare'; +import { ComponentCompareAspect } from '@teambit/component-compare'; + +import { APIReferenceAspect } from './api-reference.aspect'; +import { APICompareSection } from './api-compare.section'; + +export type APINodeRendererSlot = SlotRegistry; +export class APIReferenceUI { + constructor( + private host: string, + private apiNodeRendererSlot: APINodeRendererSlot, + private code: CodeUI, + private workspace: WorkspaceUI + ) {} + + static dependencies = [ComponentAspect, CodeAspect, WorkspaceAspect, ComponentCompareAspect]; + static runtime = UIRuntime; + static slots = [Slot.withType()]; + + getAPIPage() { + const EditorProvider = this.code.getCodeEditorProvider(); + return ( + + + + ); + } + + TaggedAPIPage = ({ componentId }: { componentId: string }) => { + return ( + + + + ); + }; + + /** + * @deprecated use registerSchemaClasses instead + * registerSchemaClasses is better for performance as it lazy-loads the schemas. + */ + registerSchemaClass(schema: SchemaNodeConstructor) { + SchemaRegistry.register(schema); + } + + registerSchemaClasses(getSchemas: () => SchemaNodeConstructor[]) { + SchemaRegistry.registerGetSchemas(getSchemas); + } + + registerAPINodeRenderer(apiNodeRenderers: APINodeRenderer[]) { + this.apiNodeRendererSlot.register(apiNodeRenderers); + } + + apiNodeRenderers: APINodeRenderer[] = defaultNodeRenderers; + + static async provider( + [componentUI, codeUI, workspaceUI, componentCompareUI]: [ComponentUI, CodeUI, WorkspaceUI, ComponentCompareUI], + _, + [apiNodeRendererSlot]: [APINodeRendererSlot], + harmony: Harmony + ) { + const { config } = harmony; + const host = String(config.get('teambit.harmony/bit')); + const apiReferenceUI = new APIReferenceUI(host, apiNodeRendererSlot, codeUI, workspaceUI); + apiReferenceUI.registerAPINodeRenderer(apiReferenceUI.apiNodeRenderers); + const apiReferenceSection = new APIRefSection(apiReferenceUI); + componentUI.registerNavigation(apiReferenceSection.navigationLink, apiReferenceSection.order); + componentUI.registerRoute(apiReferenceSection.route); + // register all default schema classes + apiReferenceUI.registerSchemaClasses(() => Object.values(Schemas)); + + // register API compare tab in component compare. insights are resolved lazily — + // aspects can register them on componentCompareUI after this provider runs. + const apiCompareSection = new APICompareSection(() => componentCompareUI.getApiDiffInsights()); + componentCompareUI.registerNavigation(apiCompareSection); + componentCompareUI.registerRoutes([apiCompareSection.route]); + + return apiReferenceUI; + } +} + +export default APIReferenceUI; + +APIReferenceAspect.addRuntime(APIReferenceUI); diff --git a/components/api-reference/esm.mjs b/components/api-reference/esm.mjs new file mode 100644 index 0000000..2718c3e --- /dev/null +++ b/components/api-reference/esm.mjs @@ -0,0 +1,7 @@ +// eslint-disable-next-line import/no-unresolved +import cjsModule from './index.js'; + +export const APIReferenceAspect = cjsModule.APIReferenceAspect; + +export default cjsModule; + diff --git a/components/api-reference/index.ts b/components/api-reference/index.ts new file mode 100644 index 0000000..d1b4c8b --- /dev/null +++ b/components/api-reference/index.ts @@ -0,0 +1,7 @@ +import { APIReferenceAspect } from './api-reference.aspect'; + +export type { APIReferenceUI, APINodeRendererSlot } from './api-reference.ui.runtime'; +export { APICompare } from './api-compare'; +export type { APIDiffResult, APIDiffChange, APIDiffDetail } from './api-compare'; +export default APIReferenceAspect; +export { APIReferenceAspect }; diff --git a/components/explorer/api-reference-explorer/api-reference-explorer.module.scss b/components/explorer/api-reference-explorer/api-reference-explorer.module.scss new file mode 100644 index 0000000..53f1d0b --- /dev/null +++ b/components/explorer/api-reference-explorer/api-reference-explorer.module.scss @@ -0,0 +1,8 @@ +.apiReferenceExplorer { + display: flex; + flex-direction: column; + overflow: auto; + height: 100%; + font-size: var(--bit-p-xs); + border-right: 2px solid var(--border-medium-color, #ededed); +} diff --git a/components/explorer/api-reference-explorer/api-reference-explorer.tsx b/components/explorer/api-reference-explorer/api-reference-explorer.tsx new file mode 100644 index 0000000..823262a --- /dev/null +++ b/components/explorer/api-reference-explorer/api-reference-explorer.tsx @@ -0,0 +1,33 @@ +import type { HTMLAttributes } from 'react'; +import React from 'react'; +import classNames from 'classnames'; +import { FileTree } from '@teambit/ui-foundation.ui.tree.file-tree'; +import { useUpdatedUrlFromQuery } from '@teambit/api-reference.hooks.use-api-ref-url'; +import type { TreeNode } from '@teambit/design.ui.tree'; + +import styles from './api-reference-explorer.module.scss'; + +export type APIReferenceExplorerProps = { + apiTree: string[]; + selectedAPIName: string; + getIcon?: (node: TreeNode) => string | undefined; +} & HTMLAttributes; + +export function APIReferenceExplorer({ apiTree, selectedAPIName, className, getIcon }: APIReferenceExplorerProps) { + return ( +
+ { + const id = node.id; + const isInternal = id.includes('_Internals'); + const sanitizedId = isInternal ? id.replace('_Internals/', '') : id; + const selectedAPI = isInternal ? sanitizedId : sanitizedId.split('/')[1]; + return useUpdatedUrlFromQuery({ selectedAPI }); + }} + files={apiTree} + selected={selectedAPIName} + getIcon={getIcon} + /> +
+ ); +} diff --git a/components/explorer/api-reference-explorer/index.ts b/components/explorer/api-reference-explorer/index.ts new file mode 100644 index 0000000..fb42245 --- /dev/null +++ b/components/explorer/api-reference-explorer/index.ts @@ -0,0 +1 @@ +export { APIReferenceExplorer, APIReferenceExplorerProps } from './api-reference-explorer'; diff --git a/components/hooks/use-api-ref-url/index.ts b/components/hooks/use-api-ref-url/index.ts new file mode 100644 index 0000000..be5769c --- /dev/null +++ b/components/hooks/use-api-ref-url/index.ts @@ -0,0 +1 @@ +export { useAPIRefParam, APIRefQueryParams, useUpdatedUrlFromQuery } from './use-api-ref-url'; diff --git a/components/hooks/use-api-ref-url/use-api-ref-url.ts b/components/hooks/use-api-ref-url/use-api-ref-url.ts new file mode 100644 index 0000000..275d5d5 --- /dev/null +++ b/components/hooks/use-api-ref-url/use-api-ref-url.ts @@ -0,0 +1,24 @@ +import { useQuery } from '@teambit/ui-foundation.ui.react-router.use-query'; +import { useLocation } from '@teambit/base-react.navigation.link'; + +export type APIRefQueryParams = { + selectedAPI?: string; +}; + +export function useAPIRefParam(key: keyof APIRefQueryParams): string | undefined { + const query = useQuery(); + const queryParam = query.get(key); + return queryParam ?? undefined; +} + +export function useUpdatedUrlFromQuery(queryParams: APIRefQueryParams): string { + const query = useQuery(); + const location = useLocation() || { pathname: '/' }; + const queryObj = Object.fromEntries(query.entries()); + + const updatedObj = { ...queryObj, ...queryParams }; + const queryString = new URLSearchParams(updatedObj).toString(); + const isOnAPIRefSubroute = location.pathname.includes('~api-reference'); + const apiRefSubRoute = isOnAPIRefSubroute ? '' : '/~api-reference'; + return `${location.pathname}${apiRefSubRoute}?${queryString}`; +} diff --git a/components/hooks/use-api-renderers/index.ts b/components/hooks/use-api-renderers/index.ts new file mode 100644 index 0000000..f0814fb --- /dev/null +++ b/components/hooks/use-api-renderers/index.ts @@ -0,0 +1,2 @@ +export { APIRefRenderersProvider, useAPIRefRenderers, APIRefRenderersContext } from './use-api-renderers'; +export type { APIRefRenderersProviderProps } from './use-api-renderers'; diff --git a/components/hooks/use-api-renderers/use-api-renderers.tsx b/components/hooks/use-api-renderers/use-api-renderers.tsx new file mode 100644 index 0000000..34d8865 --- /dev/null +++ b/components/hooks/use-api-renderers/use-api-renderers.tsx @@ -0,0 +1,30 @@ +import type { APINodeRenderer } from '@teambit/api-reference.models.api-node-renderer'; +import type { ReactNode } from 'react'; +import React, { createContext, useContext } from 'react'; + +export const APIRefRenderersContext = createContext<{ + nodeRenderers: APINodeRenderer[]; +}>({ + nodeRenderers: [], +}); + +export type APIRefRenderersProviderProps = { + children: ReactNode; + nodeRenderers: APINodeRenderer[]; +}; + +export const APIRefRenderersProvider: React.FC = ({ children, nodeRenderers }) => { + return ( + + {children} + + ); +}; + +export const useAPIRefRenderers = () => { + return useContext(APIRefRenderersContext); +}; diff --git a/components/hooks/use-api/index.ts b/components/hooks/use-api/index.ts new file mode 100644 index 0000000..3437779 --- /dev/null +++ b/components/hooks/use-api/index.ts @@ -0,0 +1 @@ +export { useAPI } from './use-api'; diff --git a/components/hooks/use-api/use-api.ts b/components/hooks/use-api/use-api.ts new file mode 100644 index 0000000..ea97cc3 --- /dev/null +++ b/components/hooks/use-api/use-api.ts @@ -0,0 +1,36 @@ +import { useDataQuery } from '@teambit/ui-foundation.ui.hooks.use-data-query'; +import { gql } from '@apollo/client'; +import { APIReferenceModel } from '@teambit/api-reference.models.api-reference-model'; +import type { APINodeRenderer } from '@teambit/api-reference.models.api-node-renderer'; + +const GET_SCHEMA = gql` + query Schema($componentId: String!, $skipInternals: Boolean) { + getHost { + id # used for GQL caching + getSchema(id: $componentId, skipInternals: $skipInternals) + } + } +`; + +export function useAPI( + componentId?: string, + apiNodeRenderers: APINodeRenderer[] = [], + options?: { + skipInternals?: boolean; + } +): { apiModel?: APIReferenceModel; loading?: boolean } { + const { data, loading } = useDataQuery(GET_SCHEMA, { + variables: { + componentId, + skipInternals: options?.skipInternals, + skip: !componentId, + }, + }); + + const apiModel = data?.getHost?.getSchema ? APIReferenceModel.from(data, apiNodeRenderers) : undefined; + + return { + loading, + apiModel, + }; +} diff --git a/components/hooks/use-schema/index.ts b/components/hooks/use-schema/index.ts new file mode 100644 index 0000000..a88bb7a --- /dev/null +++ b/components/hooks/use-schema/index.ts @@ -0,0 +1 @@ +export { useSchema } from './use-schema'; diff --git a/components/hooks/use-schema/use-schema.ts b/components/hooks/use-schema/use-schema.ts new file mode 100644 index 0000000..3b0cf00 --- /dev/null +++ b/components/hooks/use-schema/use-schema.ts @@ -0,0 +1,36 @@ +import { DataQueryResult, useDataQuery } from '@teambit/ui-foundation.ui.hooks.use-data-query'; +import { gql } from '@apollo/client'; +import { SchemaQueryResult, APIReferenceModel } from '@teambit/api-reference.models.api-reference-model'; +import { APINodeRenderer } from '@teambit/api-reference.models.api-node-renderer'; + +const GET_SCHEMA = gql` + query Schema($extensionId: String!, $componentId: String!) { + getHost(id: $extensionId) { + id # used for GQL caching + getSchema(id: $componentId) + } + } +`; + +export function useSchema( + host: string, + componentId: string, + apiNodeRenderers: APINodeRenderer[] +): { apiModel?: APIReferenceModel } & Omit< + DataQueryResult, + 'data' +> { + const { data, ...rest } = useDataQuery(GET_SCHEMA, { + variables: { + extensionId: host, + componentId, + }, + }); + + const apiModel = data?.getHost?.getSchema ? APIReferenceModel.from(data, apiNodeRenderers) : undefined; + + return { + ...rest, + apiModel, + }; +} diff --git a/components/models/api-node-renderer/api-node-renderer.ts b/components/models/api-node-renderer/api-node-renderer.ts new file mode 100644 index 0000000..704daf3 --- /dev/null +++ b/components/models/api-node-renderer/api-node-renderer.ts @@ -0,0 +1,20 @@ +import type { SchemaNode } from '@teambit/semantics.entities.semantic-schema'; +import type { ComponentType, HTMLAttributes } from 'react'; +import type { APINode, APIReferenceModel } from '@teambit/api-reference.models.api-reference-model'; + +export type APINodeRenderProps = { + apiNode: APINode; + apiRefModel: APIReferenceModel; + renderers: APINodeRenderer[]; + depth?: number; + metadata?: Record; +} & HTMLAttributes; + +export type APINodeRenderer = { + predicate: (node: SchemaNode) => boolean; + Component: ComponentType; + OverviewComponent?: ComponentType; + nodeType: string; + icon?: { url: string; name: string }; + default?: boolean; +}; diff --git a/components/models/api-node-renderer/index.ts b/components/models/api-node-renderer/index.ts new file mode 100644 index 0000000..eec4af9 --- /dev/null +++ b/components/models/api-node-renderer/index.ts @@ -0,0 +1,4 @@ +import nodeStyles from './node.module.scss'; + +export { APINodeRenderer, APINodeRenderProps } from './api-node-renderer'; +export { nodeStyles }; diff --git a/components/models/api-node-renderer/node.module.scss b/components/models/api-node-renderer/node.module.scss new file mode 100644 index 0000000..78ab79e --- /dev/null +++ b/components/models/api-node-renderer/node.module.scss @@ -0,0 +1,13 @@ +.node { + display: flex; + width: fit-content; + align-items: center; + position: relative; + font-size: var(--bit-p-xs); + word-break: normal; + padding: 4px 0px; + border-radius: 4px; + word-break: keep-all; + flex-wrap: wrap; + word-break: normal; +} diff --git a/components/models/api-reference-model/api-reference-model.ts b/components/models/api-reference-model/api-reference-model.ts new file mode 100644 index 0000000..265d62e --- /dev/null +++ b/components/models/api-reference-model/api-reference-model.ts @@ -0,0 +1,176 @@ +import type { SchemaNode, TypeRefSchema } from '@teambit/semantics.entities.semantic-schema'; +import { APISchema, ExportSchema, ModuleSchema } from '@teambit/semantics.entities.semantic-schema'; +import type { APINodeRenderer } from '@teambit/api-reference.models.api-node-renderer'; +import type { ComponentIdObj } from '@teambit/component-id'; +import { ComponentID } from '@teambit/component-id'; +import compact from 'lodash.compact'; +import head from 'lodash.head'; + +export type SchemaQueryResult = { + getHost: { + getSchema: JSON; + }; +}; + +export type APINode = { + api: T; + renderer: APINodeRenderer; + componentId: ComponentID; + exported: boolean; + alias?: string; +}; + +export class APIReferenceModel { + apiByType: Map; + apiByName: Map; + + internalAPIKey(schema: SchemaNode, internalFilePath?: string) { + return this.generateInternalAPIKey(internalFilePath || schema.location.filePath, schema.name); + } + + generateInternalAPIKey(filePath: string, name = '') { + return `${filePath}/${name}`; + } + + apiNodes: APINode[]; + taggedAPINodes: APINode[] = []; + componentId: ComponentID; + + constructor( + public _api: APISchema, + _renderers: APINodeRenderer[] + ) { + this.componentId = _api.componentId as any; + this.apiNodes = this.mapToAPINode(_api, _renderers, this.componentId); + this.apiByType = this.groupByType(this.apiNodes); + this.apiByName = this.groupByName(this.apiNodes); + this.taggedAPINodes = this.mapTaggedAPINode(_api, _renderers, this.componentId); + } + + mapTaggedAPINode(api: APISchema, renderers: APINodeRenderer[], componentId: ComponentID): APINode[] { + const { taggedModuleExports } = api; + const defaultRenderers = renderers.filter((renderer) => renderer.default); + const nonDefaultRenderers = renderers.filter((renderer) => !renderer.default); + + return taggedModuleExports + .map((schemaNode) => ({ + componentId, + api: schemaNode, + exported: true, + renderer: + nonDefaultRenderers.find((renderer) => renderer.predicate(schemaNode)) || + defaultRenderers.find((renderer) => renderer.predicate(schemaNode)), + })) + .filter((schemaNode) => schemaNode.renderer) as APINode[]; + } + + getByType(type: string): APINode[] { + return this.apiByType.get(type) ?? []; + } + + getByName(node: SchemaNode, internalFilePath?: string): APINode | undefined { + if (!node.name) return undefined; + return this.apiByName.get(node.name) ?? this.apiByName.get(this.internalAPIKey(node, internalFilePath)); + } + + groupByType(apiNodes: APINode[]): Map { + return apiNodes.reduce((accum, next) => { + const existing = accum.get(next.renderer.nodeType) || []; + accum.set(next.renderer.nodeType, existing.concat(next)); + return accum; + }, new Map()); + } + + groupByName(apiNodes: APINode[]): Map { + return apiNodes.reduce((accum, next) => { + const name = next.alias || next.api.name; + if (!name) return accum; + const key = next.exported ? name : this.internalAPIKey(next.api); + accum.set(key, next); + return accum; + }, new Map()); + } + + private isInternalTypeReference(node: SchemaNode): node is TypeRefSchema { + return node.__schema === 'TypeRefSchema' && (node as TypeRefSchema).isInternalReference(); + } + + mapToAPINode(api: APISchema, renderers: APINodeRenderer[], componentId: ComponentID): APINode[] { + const { exports: exportedSchemaNodes } = api.module; + + const collectedInternals: SchemaNode[] = []; + exportedSchemaNodes.forEach((exportedNode) => { + if (ModuleSchema.isModuleSchema(exportedNode)) { + exportedSchemaNodes.push(...exportedNode.exports); + collectedInternals.push(...exportedNode.internals); + return; + } + const targetNode = ExportSchema.isExportSchema(exportedNode) ? exportedNode.exportNode : exportedNode; + const allDescendants = targetNode.getAllNodesRecursively(); + allDescendants.forEach((descendant) => { + if (descendant !== targetNode && this.isInternalTypeReference(descendant)) { + collectedInternals.push(descendant); + } + }); + }); + + const dedupedInternalsMap = new Map(); + for (const node of collectedInternals) { + const key = this.internalAPIKey(node); + if (!dedupedInternalsMap.has(key)) { + dedupedInternalsMap.set(key, node); + } + } + const internalSchemaNodes = Array.from(dedupedInternalsMap.values()); + + const defaultRenderers = renderers.filter((renderer) => renderer.default); + const nonDefaultRenderers = renderers.filter((renderer) => !renderer.default); + + const exportedAPINodes: APINode[] = exportedSchemaNodes.map((schemaNode) => { + const targetNode = ExportSchema.isExportSchema(schemaNode) ? schemaNode.exportNode : schemaNode; + return { + componentId, + api: targetNode, + alias: ExportSchema.isExportSchema(schemaNode) ? schemaNode.alias : undefined, + exported: true, + renderer: + nonDefaultRenderers.find((renderer) => renderer.predicate(targetNode)) || + defaultRenderers.find((renderer) => renderer.predicate(targetNode)), + } as APINode; + }); + + const internalAPINodes: APINode[] = internalSchemaNodes.map((schemaNode) => { + const internalSchemaNode = + head( + compact( + api.internals + .flatMap((i) => i.exports) + .map((e) => e.findNode((s) => s.name === schemaNode.name && s.__schema !== 'TypeRefSchema')) + ) + ) || schemaNode; + + return { + componentId, + api: internalSchemaNode, + exported: false, + renderer: + nonDefaultRenderers.find((renderer) => renderer.predicate(internalSchemaNode)) || + defaultRenderers.find((renderer) => renderer.predicate(internalSchemaNode)), + } as APINode; + }); + + return exportedAPINodes.concat(internalAPINodes).filter((node) => node.renderer); + } + + static from(result: SchemaQueryResult, renderers: APINodeRenderer[] = []): APIReferenceModel { + try { + const apiSchema = APISchema.fromObject(result.getHost.getSchema); + return new APIReferenceModel(apiSchema, renderers); + } catch { + return new APIReferenceModel( + APISchema.empty(ComponentID.fromObject((result.getHost.getSchema as any).componentId as ComponentIdObj) as any), + renderers + ); + } + } +} diff --git a/components/models/api-reference-model/index.ts b/components/models/api-reference-model/index.ts new file mode 100644 index 0000000..56458b8 --- /dev/null +++ b/components/models/api-reference-model/index.ts @@ -0,0 +1 @@ +export { SchemaQueryResult, APIReferenceModel, APINode } from './api-reference-model'; diff --git a/components/overview/api-reference-table-of-contents/api-reference.table-of-contents.module.scss b/components/overview/api-reference-table-of-contents/api-reference.table-of-contents.module.scss new file mode 100644 index 0000000..518f37a --- /dev/null +++ b/components/overview/api-reference-table-of-contents/api-reference.table-of-contents.module.scss @@ -0,0 +1,80 @@ +.apiRefToc { + display: flex; + flex-direction: column; + gap: 20px; +} + +.apiRefTocGroup { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); + gap: 12px; +} + +.apiRefTocGroupC { + display: grid; + grid-template-columns: 180px 1fr; +} + +@media (min-width: 600px) { + .apiRefTocGroup { + grid-template-columns: repeat(1, 1fr); + } +} + +@media (min-width: 800px) { + .apiRefTocGroup { + grid-template-columns: repeat(2, 1fr); + } +} + +@media (min-width: 1000px) { + .apiRefTocGroup { + grid-template-columns: repeat(3, 1fr); + } +} + +@media (min-width: 1200px) { + .apiRefTocGroup { + grid-template-columns: repeat(4, 1fr); + } +} + +@media (min-width: 1400px) { + .apiRefTocGroup { + grid-template-columns: repeat(5, 1fr); + } +} + +.apiRefTocGroupName { + display: flex; +} + +.apiRefTocGroupIcon { + width: 16px; + height: 16px; + padding: 0px 8px; +} + +.apiRefTocGroupItem { + padding: 8px; + font-size: 16px; + align-items: flex-start; +} + +.apiRefTocLink { + text-decoration: none; + color: var(--bit-accent-text, #6c5ce7); + + &:hover { + color: var(--bit-accent-text-heavy, var(--bit-accent-heavy, #5d4aec)); + } +} + +.apiRefTocGroupNameContainer { + display: grid; + font-size: 16px; + font-weight: 500; + align-items: flex-start; + grid-column: 1; + padding: 8px 0px; +} diff --git a/components/overview/api-reference-table-of-contents/api-reference.table-of-contents.tsx b/components/overview/api-reference-table-of-contents/api-reference.table-of-contents.tsx new file mode 100644 index 0000000..b6901e5 --- /dev/null +++ b/components/overview/api-reference-table-of-contents/api-reference.table-of-contents.tsx @@ -0,0 +1,54 @@ +import type { HTMLAttributes } from 'react'; +import React from 'react'; +import classNames from 'classnames'; +import type { APIReferenceModel } from '@teambit/api-reference.models.api-reference-model'; +import { sortAPINodes } from '@teambit/api-reference.utils.sort-api-nodes'; +import { Link as BaseLink } from '@teambit/base-react.navigation.link'; + +import styles from './api-reference.table-of-contents.module.scss'; + +// @todo - this will be fixed as part of the @teambit/base-react.navigation.link upgrade to latest +const Link = BaseLink as any; + +export type APIReferenceTableOfContentsProps = { + apiModel: APIReferenceModel; +} & HTMLAttributes; + +export function APIReferenceTableOfContents({ apiModel, className }: APIReferenceTableOfContentsProps) { + const apiNodesGroupedByType = Array.from(apiModel.apiByType.entries()).sort(sortGroupedAPINodes); + + return ( +
+ {apiNodesGroupedByType.map(([type, nodes]) => ( +
+
+

+ {`${type} + {nodes[0]?.renderer?.nodeType} +

+
+
+ {nodes.sort(sortAPINodes).map((node) => ( +
+
+ + {node.alias || node.api.name} + +
+
+ ))} +
+
+ ))} +
+ ); +} + +function sortGroupedAPINodes(a: [string, unknown], b: [string, unknown]) { + const [aType] = a; + const [bType] = b; + return aType.localeCompare(bType); +} diff --git a/components/overview/api-reference-table-of-contents/index.ts b/components/overview/api-reference-table-of-contents/index.ts new file mode 100644 index 0000000..57afb7f --- /dev/null +++ b/components/overview/api-reference-table-of-contents/index.ts @@ -0,0 +1,2 @@ +export { APIReferenceTableOfContents } from './api-reference.table-of-contents'; +export type { APIReferenceTableOfContentsProps } from './api-reference.table-of-contents'; diff --git a/components/overview/renderers/grouped-schema-nodes-overview-summary/grouped-schema-nodes-overview-summary.module.scss b/components/overview/renderers/grouped-schema-nodes-overview-summary/grouped-schema-nodes-overview-summary.module.scss new file mode 100644 index 0000000..05bd792 --- /dev/null +++ b/components/overview/renderers/grouped-schema-nodes-overview-summary/grouped-schema-nodes-overview-summary.module.scss @@ -0,0 +1,182 @@ +.groupNodesContainer { + display: flex; + flex-direction: column; + gap: 24px; + padding: 16px; + background-color: var(--surface01-color, #f7f7f5); + border-radius: 8px; +} + +.heading { + display: grid; + grid-template-columns: 240px minmax(500px, 900px); +} + +.headingLeft { + grid-column: 1; + display: flex; + gap: 8px; +} + +.headingRight { + grid-column: 2; + display: flex; + align-items: center; + justify-content: flex-end; + > img { + width: 18px; + height: 18px; + cursor: pointer; + &.collapsed { + transform: rotate(-90deg); + } + transition: transform 0.2s ease-in-out; + } +} + +.icon { + > img { + cursor: pointer; + width: 18px; + height: 18px; + } +} + +.title { + font-size: 18px; + font-weight: 500; +} + +.signature { + min-width: 400px; + max-width: 800px; +} + +// .headingRight, +// .icon { +// display: flex; +// align-items: center; +// justify-content: flex-end; +// } + +.description { + font-size: 14px; +} + +.memberSummary { + display: flex; +} + +.propertiesOverview { + display: flex; +} + +.propertiesTitle { + font-weight: 500; + font-size: 16px; +} + +.propertiesTitle, +.memberTitle { + width: 240px; + font-size: 16px; + display: flex; + align-items: center; + text-transform: capitalize; +} + +.methodMembers { + display: flex; + flex-direction: column; + gap: 16px; +} + +.table, +.signature { + display: flex; + flex-direction: column; +} + +.icon { + cursor: pointer; + flex-shrink: 0; + > img { + height: 18px; + width: 18px; + &.collapsed { + transform: rotate(-90deg); + } + transition: transform 0.2s ease-in-out; + } +} + +.expandedRowDetails { + display: flex; + flex-direction: column; + flex: 1; + gap: 24px; +} + +.description { + font-size: 14px; + margin-bottom: 16px; + font-style: italic; +} + +.subtitle { + font-weight: 500; + text-transform: uppercase; + font-size: var(--bit-p-xxs); + padding-right: 16px; + margin-bottom: 12px; +} + +.paramHeading { + padding: 8px 0px 16px 0px; +} + +.returnContainer { + margin-bottom: 16px; +} + +.memberDetails { + display: grid; + grid-template-columns: 240px minmax(400px, 800px) 50px; + width: 100%; + align-items: center; + gap: 16px 0px; +} + +.collapsedRow { + align-items: center; +} + +.collapsedRow, +.expandedRow { + display: contents; + padding: 16px 0px; +} + +.memberTitle, +.leftPlaceholder { + grid-column: 1; +} + +.signature, +.expandedRowDetails, +.icon { + grid-column: 2; +} + +.icon { + grid-column: 3; + justify-self: end; +} + +.expandedRow { + grid-column: 1 / span 3; +} + +.table { + padding-top: 8px; +} diff --git a/components/overview/renderers/grouped-schema-nodes-overview-summary/grouped-schema-nodes-overview-summary.tsx b/components/overview/renderers/grouped-schema-nodes-overview-summary/grouped-schema-nodes-overview-summary.tsx new file mode 100644 index 0000000..0d9fab4 --- /dev/null +++ b/components/overview/renderers/grouped-schema-nodes-overview-summary/grouped-schema-nodes-overview-summary.tsx @@ -0,0 +1,259 @@ +import type { HTMLAttributes } from 'react'; +import React from 'react'; +import type { SchemaNode, EnumMemberSchema } from '@teambit/semantics.entities.semantic-schema'; +import { SetAccessorSchema } from '@teambit/semantics.entities.semantic-schema'; +import { + groupByNodeSignatureType, + sortSignatureType, +} from '@teambit/api-reference.utils.group-schema-node-by-signature'; +import { transformSignature } from '@teambit/api-reference.utils.schema-node-signature-transform'; +import { HeadingRow } from '@teambit/documenter.ui.table-heading-row'; +import type { APINodeRenderProps } from '@teambit/api-reference.models.api-node-renderer'; +import { nodeStyles } from '@teambit/api-reference.models.api-node-renderer'; +import { VariableNodeSummary, EnumMemberSummary } from '@teambit/api-reference.renderers.schema-node-member-summary'; +import { parameterRenderer as defaultParamRenderer } from '@teambit/api-reference.renderers.parameter'; +import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; +import defaultTheme from '@teambit/api-reference.utils.custom-prism-syntax-highlighter-theme'; +import { Link as BaseLink } from '@teambit/base-react.navigation.link'; +import pluralize from 'pluralize'; +import classnames from 'classnames'; + +import styles from './grouped-schema-nodes-overview-summary.module.scss'; + +// @todo - this will be fixed as part of the @teambit/base-react.navigation.link upgrade to latest +const Link = BaseLink as any; + +export type SchemaNodesSummaryProps = { + name: string; + // signature?: string; + description?: string; + icon?: { name: string; url: string }; + nodes: SchemaNode[]; + apiNodeRendererProps: APINodeRenderProps; + headings?: Record; + renderTable?(type: string, member: SchemaNode, headings?: string[]): React.ReactNode; +} & HTMLAttributes; + +const DEFAULT_HEADINGS = { + methods: ['name', 'signature', 'description'], + constructors: ['name', 'signature', 'description'], + 'enum members': ['name', 'description'], + properties: ['name', 'type', 'default', 'description'], + setters: ['name', 'signature', 'description'], + default: ['name', 'type', 'description'], +}; + +const defaultTableRenderer = function DefaultTableRendererWrapper(apiNodeRendererProps: APINodeRenderProps) { + return function DefaultTableRenderer(type: string, member: SchemaNode, headings: string[]) { + const typeId = type && encodeURIComponent(type); + + if (type === 'enum members') { + return ( + + ); + } + + return ( + + ); + }; +}; + +export function SchemaNodesSummary({ + name, + description, + icon, + nodes, + // signature, + apiNodeRendererProps, + headings: headingsFromProps = {}, + // skipNode, + // skipGrouping, + renderTable = defaultTableRenderer(apiNodeRendererProps), + className, + ...rest +}: SchemaNodesSummaryProps) { + const hasNodes = nodes.length > 0; + const headings = { + ...DEFAULT_HEADINGS, + ...headingsFromProps, + }; + + const groupedNodes = hasNodes + ? Array.from(groupByNodeSignatureType(nodes).entries()).sort(sortSignatureType) + : (hasNodes && [['', nodes] as [string, SchemaNode[]]]) || []; + + const rootRef = React.useRef(null); + + return ( +
+
+
+ {icon && ( +
+ {icon.name} +
+ )} +
+ {name} +
+
+
+ {description &&
{description}
} + {groupedNodes.map(([type, groupedMembersByType], index) => { + const skipRenderingTable = type === 'methods' || type === 'constructors' || type === 'setters'; + const _headings = (type && headings[type]) || headings.default; + + return ( +
+ {!skipRenderingTable && ( +
+
+ {type === 'enum members' ? 'Members' : pluralize(type ?? 'properties')} +
+
+ + {groupedMembersByType.map((member) => { + // @ts-expect-error - version skew: local SchemaNode has diff() but npm-published version doesn't yet + return renderTable(type ?? '', member, _headings); + })} +
+
+ )} + {skipRenderingTable && ( +
+ {groupedMembersByType.map((member) => ( + + ))} +
+ )} +
+ ); + })} +
+ ); +} + +function SchemaMethodMember({ + member, + apiNodeRendererProps, +}: { + member: SchemaNode; + apiNodeRendererProps: APINodeRenderProps; +}) { + const memberSignature = + member.__schema === SetAccessorSchema.name + ? `(${(member as SetAccessorSchema).param.toString()}) => void` + : (transformSignature(member)?.split(member.name ?? '')[1] ?? member.signature); + + const { renderers } = apiNodeRendererProps; + const { doc } = member; + const returnType = (member as any).returnType; + const returnTypeRenderer = returnType && renderers.find((renderer) => renderer.predicate(returnType)); + const paramTypeHeadings = ['Parameter', 'type', 'default', 'description']; + const params = (member as any).params || [(member as any).param]; + const filePath = apiNodeRendererProps.apiNode.api.location.filePath; + const lang = React.useMemo(() => { + const langFromFileEnding = filePath?.split('.').pop(); + if (langFromFileEnding === 'scss' || langFromFileEnding === 'sass') return 'css'; + if (langFromFileEnding === 'mdx') return 'md'; + return langFromFileEnding; + }, [filePath]); + + return ( +
+
+
{member.name}
+ {memberSignature && ( +
+ + {memberSignature} + +
+ )} +
+ +
+
+
+ {doc?.comment &&
{doc?.comment || ''}
} + {params.length > 0 && ( +
+ + {params.map((param) => { + const paramRenderer = renderers.find((renderer) => renderer.predicate(param)); + if (paramRenderer?.Component) { + return ( + + ); + } + return ( + + ); + })} +
+ )} + {returnType && ( +
+

Returns

+ {(returnTypeRenderer && ( + + )) ||
{returnType.toString()}
} +
+ )} +
+
+
+ ); +} diff --git a/components/overview/renderers/grouped-schema-nodes-overview-summary/index.ts b/components/overview/renderers/grouped-schema-nodes-overview-summary/index.ts new file mode 100644 index 0000000..463fd99 --- /dev/null +++ b/components/overview/renderers/grouped-schema-nodes-overview-summary/index.ts @@ -0,0 +1,2 @@ +export { SchemaNodesSummary } from './grouped-schema-nodes-overview-summary'; +export type { SchemaNodesSummaryProps } from './grouped-schema-nodes-overview-summary'; diff --git a/components/renderers/api-node-details/api-node-details.module.scss b/components/renderers/api-node-details/api-node-details.module.scss new file mode 100644 index 0000000..b8fafb1 --- /dev/null +++ b/components/renderers/api-node-details/api-node-details.module.scss @@ -0,0 +1,96 @@ +.apiNodeDetailsContainer { + padding: 24px 0px; + display: flex; + height: calc(100% - 40px); +} +.apiDetails { + overflow-y: auto; + width: 100%; + padding-right: 16px; +} +.schemaNodesIndex { + flex: 0; + height: 100%; +} +.apiNodeDetailsNameContainer { + display: flex; + flex-direction: row; + padding-bottom: 24px; + align-items: center; +} +.apiNodeDetailsName { + padding-right: 8px; +} +.apiTypeIcon { + img { + height: 18px; + } + padding-right: 8px; +} +.codeEditorContainer { + transition: min-height 0.4s ease-in-out; + > section { + overflow: hidden; + border-radius: 8px; + // to prevent monaco from overflowing from its parent + width: unset !important; + } +} +.editor { + background-color: var(--editor-bg-color, #1e1e1e); +} +.apiNodeDetailsComment { + font-size: var(--bit-p-xs); + padding-bottom: 24px; +} +.apiNodeDetailsSignatureContainer { + display: flex; + flex-direction: column; + padding-bottom: 24px; +} +.apiNodeDetailsExample { + padding-bottom: 24px; +} +.apiNodeDetailsExampleTitle { + padding: 16px 0px; +} +.apiNodeImplementationDrawer { + width: 100%; +} +.apiNodeDetailsLocationContainer { + display: flex; + flex-direction: row; + align-items: center; + line-height: 16px; + padding-bottom: 24px; +} + +.apiNodeImplementationCodeView { + padding: 16px 0px; + > h1 { + display: none !important; + } +} +.apiNodeDetailsLocation { + display: flex; + width: 100%; +} +.apiNodeDetailsLocationIcon { + display: flex; + padding-right: 8px; + img { + height: 14px; + } +} +.apiNodeDetailsLocationLink { + max-width: fit-content; + font-size: var(--bit-p-xs); +} +.apiNodeDetailsMembersContainer { + padding-bottom: 16px; +} + +.apiNodeDetailsLink { + font-size: var(--bit-p-xs); + padding-bottom: 24px; +} diff --git a/components/renderers/api-node-details/api-node-details.tsx b/components/renderers/api-node-details/api-node-details.tsx new file mode 100644 index 0000000..6fec2ce --- /dev/null +++ b/components/renderers/api-node-details/api-node-details.tsx @@ -0,0 +1,338 @@ +import React, { useState, useRef, useCallback } from 'react'; +import { H6 } from '@teambit/documenter.ui.heading'; +import { CodeEditor, useCodeEditor } from '@teambit/code.ui.code-editor'; +import { useLocation } from '@teambit/base-react.navigation.link'; +import { defaultCodeEditorOptions } from '@teambit/api-reference.utils.code-editor-options'; +import classnames from 'classnames'; +import type { APINodeRenderProps } from '@teambit/api-reference.models.api-node-renderer'; +import { useQuery } from '@teambit/ui-foundation.ui.react-router.use-query'; +import type { APIRefQueryParams } from '@teambit/api-reference.hooks.use-api-ref-url'; +import { useNavigate } from 'react-router-dom'; +import type { APINode } from '@teambit/api-reference.models.api-reference-model'; +import { SchemaNodesIndex } from '@teambit/api-reference.renderers.schema-nodes-index'; + +import { extractCodeBlock } from './extract-code-block'; +import styles from './api-node-details.module.scss'; + +const INDEX_THRESHOLD_WIDTH = 600; + +export type APINodeDetailsProps = APINodeRenderProps & { + displaySignature?: string; + options?: { + hideIndex?: boolean; + }; +}; + +export function APINodeDetails({ + apiNode: { + api: { + name, + signature: defaultSignature, + doc, + location: { filePath }, + }, + }, + displaySignature, + children, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + renderers, + apiRefModel, + options, + ...rest +}: APINodeDetailsProps) { + const routerLocation = useLocation(); + const query = useQuery(); + const navigate = useNavigate(); + const Editor = useCodeEditor(); + + const signatureEditorRef = useRef(null); + const signatureMonacoRef = useRef(null); + + const exampleEditorRef = useRef(null); + const exampleMonacoRef = useRef(null); + + const routeToAPICmdId = useRef(null); + const apiUrlToRoute = useRef(null); + + const hoverProviderDispose = useRef(null); + + const rootRef = useRef(null) as React.MutableRefObject; + const apiRef = useRef(null); + + const signatureContainerRef = useRef(null); + const exampleContainerRef = useRef(null); + + const [containerSize] = useState<{ width?: number; height?: number }>({ + width: undefined, + height: undefined, + }); + + const currentQueryParams = query.toString(); + + const indexHidden = (containerSize.width ?? 0) < INDEX_THRESHOLD_WIDTH; + + const example = (doc?.tags || []).find((tag) => tag.tagName === 'example'); + const comment = + doc?.comment ?? doc?.tags?.filter((tag) => tag.comment).reduce((acc, tag) => acc.concat(`${tag.comment}\n`), ''); + + const linkComment = doc?.tags?.find((tag) => tag.tagName === 'link')?.comment; + + let linkPlaceholder: string | undefined; + let linkURL: string | undefined; + + if (linkComment) { + const parts = linkComment.split(' '); + linkURL = parts.find((part) => part.startsWith('http')); + if (linkURL) { + linkPlaceholder = parts.filter((part) => part !== linkURL).join(' '); + } else { + linkPlaceholder = parts.join(' '); + } + } + + const signature = displaySignature || defaultSignature; + + const getAPINodeUrl = useCallback((queryParams: APIRefQueryParams) => { + const queryObj = Object.fromEntries(query.entries()); + const updatedObj = { ...queryObj, ...queryParams }; + const queryString = new URLSearchParams(updatedObj).toString(); + return `${routerLocation?.pathname || '/'}?${queryString}`; + }, []); + + const hoverProvider = useCallback((model, position) => { + const word = model.getWordAtPosition(position); + const wordApiNode: APINode | undefined = word + ? apiRefModel?.apiByName?.get(word.word) || + apiRefModel?.apiByName?.get(apiRefModel.generateInternalAPIKey(filePath, word.word)) + : undefined; + const wordApiUrl = wordApiNode + ? getAPINodeUrl({ + selectedAPI: wordApiNode.exported + ? wordApiNode.api.name + : apiRefModel.generateInternalAPIKey(filePath, word.word), + }) + : null; + apiUrlToRoute.current = wordApiUrl; + if (!wordApiUrl || wordApiNode?.api.name === name) return undefined; + const contents = [ + { + value: `[View ${word.word} API](command:${routeToAPICmdId.current})`, + isTrusted: true, + }, + ]; + return { + contents, + }; + }, []); + + const getDisplayedLineCount = (editorInstance, containerWidth, monacoRef) => { + if (!monacoRef.current) return 0; + + const model = editorInstance.getModel(); + + if (!model) { + return 0; + } + + const lineCount = model.getLineCount(); + + let displayedLines = 0; + + const lineWidth = editorInstance.getOption(monacoRef.current.editor.EditorOption.wordWrapColumn); + const fontWidthApproximation = 8; + + for (let lineNumber = 1; lineNumber <= lineCount; lineNumber += 1) { + const line = model.getLineContent(lineNumber); + const length = line.length || 1; + const lineFitsContainer = length * fontWidthApproximation <= containerWidth; + const wrappedLineCount = (lineFitsContainer ? 1 : Math.ceil(length / lineWidth)) || 1; + displayedLines += wrappedLineCount; + } + + return displayedLines; + }; + + const updateEditorHeight = + (editorRef: React.MutableRefObject, monacoRef: React.MutableRefObject) => () => { + if (!monacoRef.current) return undefined; + + const editor = editorRef.current; + + if (!editor) { + return undefined; + } + + const lineHeight = editor.getOption(monacoRef.current.editor.EditorOption.lineHeight); + + const paddingTop = editor.getOption(monacoRef.current.editor.EditorOption.padding)?.top || 0; + const paddingBottom = editor.getOption(monacoRef.current.editor.EditorOption.padding)?.bottom || 0; + const glyphMargin = editor.getOption(monacoRef.current.editor.EditorOption.glyphMargin); + const lineNumbers = editor.getOption(monacoRef.current.editor.EditorOption.lineNumbers); + + const glyphMarginHeight = glyphMargin ? lineHeight : 0; + const lineNumbersHeight = lineNumbers.renderType !== 0 ? lineHeight : 0; + + const containerWidth = editor.getLayoutInfo().contentWidth; + const displayedLines = getDisplayedLineCount(editor, containerWidth, monacoRef); + + const contentHeight = + displayedLines * lineHeight + paddingTop + paddingBottom + glyphMarginHeight + lineNumbersHeight; + + const domNode = editor.getDomNode()?.parentElement; + + if (!domNode) { + return undefined; + } + + const newHeight = `${contentHeight}px`; + if (domNode.style.height === newHeight) { + return undefined; + } + domNode.style.height = newHeight; + editorRef.current?.layout(); + // setHeight(() => newHeight); + return undefined; + }; + + // const updateEditorHeight = _.throttle(_updateEditorHeight, 300) as _.DebouncedFunc; + + const handleEditorDidMount: ( + monacoRef: React.MutableRefObject, + editorRef: React.MutableRefObject, + containerRef: React.MutableRefObject, + setHeight?: React.Dispatch>, + onMount?: (monaco: any, editor: any) => void, + onUnMount?: () => void + ) => any = React.useCallback( + (monacoRef, editorRef, containerRef, setHeight, onMount, unMount) => (editor, _monaco) => { + /** + * disable syntax check + * ts cant validate all types because imported files aren't available to the editor + */ + monacoRef.current = _monaco; + editorRef.current = editor; + + monacoRef.current.languages?.typescript?.typescriptDefaults?.setDiagnosticsOptions({ + noSemanticValidation: true, + noSyntaxValidation: true, + }); + + monacoRef.current?.languages.typescript.typescriptDefaults.setCompilerOptions({ + jsx: monacoRef.current.languages.typescript.JsxEmit.Preserve, + target: monacoRef.current.languages.typescript.ScriptTarget.ES2020, + esModuleInterop: true, + }); + + monacoRef.current.editor.defineTheme('bit', { + base: 'vs-dark', + inherit: true, + rules: [], + colors: { + 'scrollbar.shadow': '#222222', + 'diffEditor.insertedTextBackground': '#1C4D2D', + 'diffEditor.removedTextBackground': '#761E24', + 'editor.selectionBackground': '#5A5A5A', + 'editor.overviewRulerBorder': '#6a57fd', + 'editor.lineHighlightBorder': '#6a57fd', + }, + }); + + monacoRef.current.editor.setTheme('bit'); + + onMount?.(monacoRef.current, editorRef.current); + + updateEditorHeight(editorRef, monacoRef)(); + + editor.onDidDispose(() => { + unMount?.(); + }); + }, + [] + ); + + return ( + /** + * the key is set to the the url params to force it to re-render when the query params change + * otherwise the rootRef never changes and index is unable to auto detect elements + */ +
+
+ {comment &&
{comment}
} + {linkComment && ( +
+ {linkPlaceholder && {linkPlaceholder}: } + + {linkURL} + +
+ )} + {signature && ( +
+ { + signatureMonacoRef.current = _monaco; + }} + onMount={handleEditorDidMount( + signatureMonacoRef, + signatureEditorRef, + signatureContainerRef, + undefined, + (_monaco, _editor) => { + routeToAPICmdId.current = + _editor.addCommand(0, () => { + apiUrlToRoute.current && navigate(apiUrlToRoute.current); + }) ?? null; + + if (!hoverProviderDispose.current) { + hoverProviderDispose.current = _monaco.languages.registerHoverProvider('typescript', { + provideHover: hoverProvider, + }); + } + }, + () => { + hoverProviderDispose.current?.dispose(); + } + )} + /> +
+ )} + {example && example.comment && ( +
+
Example
+
+ { + exampleMonacoRef.current = _monaco; + }} + onMount={handleEditorDidMount(exampleMonacoRef, exampleEditorRef, exampleContainerRef)} + /> +
+
+ )} + {children} +
+ {!options?.hideIndex && !indexHidden && ( + + )} +
+ ); +} diff --git a/components/renderers/api-node-details/extract-code-block.spec.ts b/components/renderers/api-node-details/extract-code-block.spec.ts new file mode 100644 index 0000000..590e49f --- /dev/null +++ b/components/renderers/api-node-details/extract-code-block.spec.ts @@ -0,0 +1,21 @@ +import { extractCodeBlock } from './extract-code-block'; + +describe('extractCodeBlock', () => { + it('should extract code block with language specifier', () => { + const text = '```typescript\nconst foo = "bar";\n```'; + const result = extractCodeBlock(text); + expect(result).toEqual({ lang: 'typescript', code: 'const foo = "bar";\n' }); + }); + + it('should extract code block without language specifier', () => { + const text = '```\nconst foo = "bar";\n```'; + const result = extractCodeBlock(text); + expect(result).toEqual({ lang: '', code: 'const foo = "bar";\n' }); + }); + + it('should return null if no code block is found', () => { + const text = 'const foo = "bar";'; + const result = extractCodeBlock(text); + expect(result).toBeNull(); + }); +}); diff --git a/components/renderers/api-node-details/extract-code-block.ts b/components/renderers/api-node-details/extract-code-block.ts new file mode 100644 index 0000000..d961ab1 --- /dev/null +++ b/components/renderers/api-node-details/extract-code-block.ts @@ -0,0 +1,23 @@ +/** + * Extracts the code block and its language specifier enclosed between triple backticks (```) from a given text string. + * + * @param text - The text string from which to extract the code block. + * + * @returns An object containing the extracted code and language specifier, or null if no match is found. + */ +export function extractCodeBlock(text: string): { lang: string; code: string } | null { + let processedText = text; + if (text.endsWith(';') && !text.endsWith('```')) { + processedText = text.slice(0, -1) + '```'; + } + const regex = /```([\w+-]*)\s*([\s\S]*?)```/; + + const match = processedText.match(regex); + + if (match) { + const lang = match[1]; + const code = match[2]; + return { lang, code }; + } + return null; +} diff --git a/components/renderers/api-node-details/index.ts b/components/renderers/api-node-details/index.ts new file mode 100644 index 0000000..f1d9dd8 --- /dev/null +++ b/components/renderers/api-node-details/index.ts @@ -0,0 +1,2 @@ +export { APINodeDetails, APINodeDetailsProps } from './api-node-details'; +export { extractCodeBlock } from './extract-code-block'; diff --git a/components/renderers/class/class.renderer.module.scss b/components/renderers/class/class.renderer.module.scss new file mode 100644 index 0000000..1ffb193 --- /dev/null +++ b/components/renderers/class/class.renderer.module.scss @@ -0,0 +1,17 @@ +.members { + display: flex; + flex-direction: column; + gap: 20px; +} + +.decorators { + display: flex; + flex-direction: column; + gap: 18px; +} + +.decoratorsTitle { + font-size: var(--bit-p-lg, '18px'); + font-weight: 500; + text-transform: capitalize; +} diff --git a/components/renderers/class/class.renderer.tsx b/components/renderers/class/class.renderer.tsx new file mode 100644 index 0000000..6622464 --- /dev/null +++ b/components/renderers/class/class.renderer.tsx @@ -0,0 +1,85 @@ +import React from 'react'; +import { ClassSchema } from '@teambit/semantics.entities.semantic-schema'; +import type { APINodeRenderProps, APINodeRenderer } from '@teambit/api-reference.models.api-node-renderer'; +import { APINodeDetails } from '@teambit/api-reference.renderers.api-node-details'; +import { GroupedSchemaNodesSummary } from '@teambit/api-reference.renderers.grouped-schema-nodes-summary'; +import { SchemaNodesSummary } from '@teambit/api-reference.overview.renderers.grouped-schema-nodes-overview-summary'; +import styles from './class.renderer.module.scss'; + +export const classRenderer: APINodeRenderer = { + predicate: (node) => node.__schema === ClassSchema.name, + Component: ClassComponent, + OverviewComponent: ClassOverviewComponent, + nodeType: 'Classes', + icon: { name: 'Class', url: 'https://static.bit.dev/api-reference/class.svg' }, + default: true, +}; + +function ClassComponent(props: APINodeRenderProps) { + const { + apiNode: { api }, + metadata, + renderers, + // depth + } = props; + const classNode = api as ClassSchema; + const { extendsNodes, implementNodes, signature, members, decorators } = classNode; + + if (metadata?.columnView?.[api.__schema]) { + // todo handle when recursively rendering a class + return {classNode.toString()}; + } + + const extendsSignature = extendsNodes?.[0]?.name; + const implementsDefinition = implementNodes?.[0]?.name; + const hasDecorators = decorators?.length; + const displaySignature = `${ + hasDecorators ? decorators.reduce((acc, decorator) => acc.concat(`${decorator.toString()}\n`), '') : '' + }${signature}${(extendsSignature && ' '.concat(extendsSignature)) || ''} ${implementsDefinition || ''}`; + + return ( + +
+ {hasDecorators && ( +
+
Decorators
+ {decorators.map((decorator) => { + const decoratorRenderer = renderers.find((renderer) => renderer.predicate(decorator)); + const DecoratorComponent = decoratorRenderer?.Component; + if (!DecoratorComponent) return null; + return ( + + ); + })} +
+ )} + +
+
+ ); +} + +function ClassOverviewComponent(props: APINodeRenderProps) { + const { + apiNode: { api, renderer }, + } = props; + const classNode = api as ClassSchema; + const { members, doc } = classNode; + + const icon = renderer.icon; + const description = + doc?.comment ?? doc?.tags?.filter((tag) => tag.comment).reduce((acc, tag) => acc.concat(`${tag.comment}\n`), ''); + return ( + + ); +} diff --git a/components/renderers/class/index.ts b/components/renderers/class/index.ts new file mode 100644 index 0000000..ce5022a --- /dev/null +++ b/components/renderers/class/index.ts @@ -0,0 +1 @@ +export { classRenderer } from './class.renderer'; diff --git a/components/renderers/decorator/decorator.renderer.module.scss b/components/renderers/decorator/decorator.renderer.module.scss new file mode 100644 index 0000000..2956620 --- /dev/null +++ b/components/renderers/decorator/decorator.renderer.module.scss @@ -0,0 +1,10 @@ +.decorator { + display: flex; + flex-direction: column; + gap: 12px; +} + +.decoratorTitle { + font-size: var(--bit-p-sm, '14px'); + font-weight: 500; +} diff --git a/components/renderers/decorator/decorator.renderer.tsx b/components/renderers/decorator/decorator.renderer.tsx new file mode 100644 index 0000000..3f9c428 --- /dev/null +++ b/components/renderers/decorator/decorator.renderer.tsx @@ -0,0 +1,55 @@ +import React from 'react'; +import type { APINodeRenderProps, APINodeRenderer } from '@teambit/api-reference.models.api-node-renderer'; +import { DecoratorSchema } from '@teambit/semantics.entities.semantic-schema'; +import { GroupedSchemaNodesSummary } from '@teambit/api-reference.renderers.grouped-schema-nodes-summary'; +import { TableRow } from '@teambit/documenter.ui.table-row'; +import styles from './decorator.renderer.module.scss'; + +export const decoratorRenderer: APINodeRenderer = { + predicate: (node) => node.__schema === DecoratorSchema.name, + Component: DecoratorComponent, + nodeType: 'Decorators', + default: true, +}; + +function DecoratorComponent(props: APINodeRenderProps) { + const { + apiNode: { api }, + } = props; + const decoratorNode = api as DecoratorSchema; + const { name, args } = decoratorNode; + const childrenNodes = decoratorNode.args?.reduce((acc, arg) => acc.concat(arg.getNodes()), [] as any[]); + + return ( +
+
@{name}
+ {(args?.length && ( + { + return ( + + ); + }} + nodes={childrenNodes ?? []} + apiNodeRendererProps={props} + /> + )) || + null} +
+ ); +} diff --git a/components/renderers/decorator/index.ts b/components/renderers/decorator/index.ts new file mode 100644 index 0000000..888943f --- /dev/null +++ b/components/renderers/decorator/index.ts @@ -0,0 +1 @@ +export { decoratorRenderer } from './decorator.renderer'; diff --git a/components/renderers/default-node-renderers/index.ts b/components/renderers/default-node-renderers/index.ts new file mode 100644 index 0000000..1c3cd14 --- /dev/null +++ b/components/renderers/default-node-renderers/index.ts @@ -0,0 +1,60 @@ +import { classRenderer } from '@teambit/api-reference.renderers.class'; +import { interfaceRenderer } from '@teambit/api-reference.renderers.interface'; +import { typeRenderer } from '@teambit/api-reference.renderers.type'; +import { functionRenderer } from '@teambit/api-reference.renderers.function'; +import { enumRenderer } from '@teambit/api-reference.renderers.enum'; +import { variableRenderer } from '@teambit/api-reference.renderers.variable'; +import { unresolvedRenderer } from '@teambit/api-reference.renderers.unresolved'; +import { typeRefRenderer } from '@teambit/api-reference.renderers.type-ref'; +import { typeUnionRenderer } from '@teambit/api-reference.renderers.type-union'; +import { typeIntersectionRenderer } from '@teambit/api-reference.renderers.type-intersection'; +import { typeLiteralRenderer } from '@teambit/api-reference.renderers.type-literal'; +import { parameterRenderer } from '@teambit/api-reference.renderers.parameter'; +import { inferenceTypeRenderer } from '@teambit/api-reference.renderers.inference-type'; +import { typeArrayRenderer } from '@teambit/api-reference.renderers.type-array'; +import { tupleTypeRenderer } from '@teambit/api-reference.renderers.tuple-type'; +import { thisRenderer } from '@teambit/api-reference.renderers.this'; +import { decoratorRenderer } from '@teambit/api-reference.renderers.decorator'; +import type { APINodeRenderer } from '@teambit/api-reference.models.api-node-renderer'; + +export const defaultNodeRenderers: APINodeRenderer[] = [ + classRenderer, + interfaceRenderer, + typeRenderer, + functionRenderer, + enumRenderer, + variableRenderer, + unresolvedRenderer, + typeRefRenderer, + typeUnionRenderer, + typeIntersectionRenderer, + typeLiteralRenderer, + parameterRenderer, + inferenceTypeRenderer, + typeArrayRenderer, + tupleTypeRenderer, + thisRenderer, + decoratorRenderer, +]; + +export { + classRenderer, + interfaceRenderer, + typeRenderer, + functionRenderer, + enumRenderer, + variableRenderer, + unresolvedRenderer, + typeRefRenderer, + typeUnionRenderer, + typeIntersectionRenderer, + typeLiteralRenderer, + parameterRenderer, + inferenceTypeRenderer, + typeArrayRenderer, + tupleTypeRenderer, + thisRenderer, + decoratorRenderer, +}; + +export default defaultNodeRenderers; diff --git a/components/renderers/enum/enum.renderer.module.scss b/components/renderers/enum/enum.renderer.module.scss new file mode 100644 index 0000000..e69de29 diff --git a/components/renderers/enum/enum.renderer.tsx b/components/renderers/enum/enum.renderer.tsx new file mode 100644 index 0000000..284076d --- /dev/null +++ b/components/renderers/enum/enum.renderer.tsx @@ -0,0 +1,50 @@ +import React from 'react'; +import { EnumSchema } from '@teambit/semantics.entities.semantic-schema'; +import type { APINodeRenderProps, APINodeRenderer } from '@teambit/api-reference.models.api-node-renderer'; +import { APINodeDetails } from '@teambit/api-reference.renderers.api-node-details'; +import { GroupedSchemaNodesSummary } from '@teambit/api-reference.renderers.grouped-schema-nodes-summary'; +import { SchemaNodesSummary } from '@teambit/api-reference.overview.renderers.grouped-schema-nodes-overview-summary'; + +export const enumRenderer: APINodeRenderer = { + predicate: (node) => node.__schema === EnumSchema.name, + Component: EnumComponent, + OverviewComponent: EnumOverviewComponent, + nodeType: 'Enums', + icon: { name: 'Enum', url: 'https://static.bit.dev/api-reference/array.svg' }, + default: true, +}; + +function EnumComponent(props: APINodeRenderProps) { + const { + apiNode: { api }, + } = props; + const enumNode = api as EnumSchema; + const { members } = enumNode; + + return ( + + + + ); +} + +function EnumOverviewComponent(props: APINodeRenderProps) { + const { + apiNode: { api, renderer }, + } = props; + const enumNode = api as EnumSchema; + const { members, doc } = enumNode; + + const icon = renderer.icon; + const description = + doc?.comment ?? doc?.tags?.filter((tag) => tag.comment).reduce((acc, tag) => acc.concat(`${tag.comment}\n`), ''); + return ( + + ); +} diff --git a/components/renderers/enum/index.ts b/components/renderers/enum/index.ts new file mode 100644 index 0000000..50a7c95 --- /dev/null +++ b/components/renderers/enum/index.ts @@ -0,0 +1 @@ +export { enumRenderer } from './enum.renderer'; diff --git a/components/renderers/function/function.renderer.module.scss b/components/renderers/function/function.renderer.module.scss new file mode 100644 index 0000000..2d02e1a --- /dev/null +++ b/components/renderers/function/function.renderer.module.scss @@ -0,0 +1,58 @@ +.container { + display: flex; + flex-direction: column; + padding-bottom: 16px; +} + +.title { + padding-bottom: 16px; + font-size: var(--bit-p-md); + font-weight: 500; +} + +.containerList { + padding-left: 4px; +} + +.subTitle { + padding-bottom: 16px; + font-size: var(--bit-p-sm); + font-weight: 400; +} + +.value { + font-size: var(--bit-p-xs); + background: var(--bit-bg-dent); + padding: 16px; + border-radius: 6px; +} + +.typeParams { + padding-top: 16px; +} + +.bold { + font-weight: 500; +} + +.table { + > div { + grid-gap: 16px 24px; + } +} + +.returnType { + font-size: var(--bit-p-xxs); + // color: #397300; + display: flex; + color: #4646c6; +} + +.docComment { + font-size: var(--bit-p-xs); + padding-bottom: 16px; +} + +.paramHeading { + padding: 8px 0px 16px 0px; +} diff --git a/components/renderers/function/function.renderer.tsx b/components/renderers/function/function.renderer.tsx new file mode 100644 index 0000000..5149483 --- /dev/null +++ b/components/renderers/function/function.renderer.tsx @@ -0,0 +1,102 @@ +import React from 'react'; +import { FunctionLikeSchema, TagName } from '@teambit/semantics.entities.semantic-schema'; +import type { APINodeRenderProps, APINodeRenderer } from '@teambit/api-reference.models.api-node-renderer'; +import { nodeStyles } from '@teambit/api-reference.models.api-node-renderer'; +import { APINodeDetails } from '@teambit/api-reference.renderers.api-node-details'; +import classnames from 'classnames'; +import { parameterRenderer as defaultParamRenderer } from '@teambit/api-reference.renderers.parameter'; +import { HeadingRow } from '@teambit/documenter.ui.table-heading-row'; + +import styles from './function.renderer.module.scss'; + +export const functionRenderer: APINodeRenderer = { + predicate: (node) => node.__schema === FunctionLikeSchema.name, + Component: FunctionComponent, + nodeType: 'Functions', + icon: { name: 'Function', url: 'https://static.bit.dev/api-reference/function.svg' }, + default: true, +}; + +function FunctionComponent(props: APINodeRenderProps) { + const { + apiNode: { api }, + renderers, + // apiRefModel, + } = props; + const functionNode = api as FunctionLikeSchema; + const { returnType, params, typeParams } = functionNode; + const returnTypeRenderer = renderers.find((renderer) => renderer.predicate(returnType)); + + if (props.metadata?.[api.__schema]?.columnView) { + return
{api.toString()}
; + } + + const Params = params.map((param) => { + const paramRenderer = renderers.find((renderer) => renderer.predicate(param)); + + const ParamComponent = paramRenderer?.Component ? ( + + ) : ( + + ); + + return ParamComponent; + }); + + const returnDocComment = api.doc?.findTag(TagName.return)?.comment; + + return ( + + {typeParams && ( +
+
Type Parameters
+
+ {typeParams.map((typeParam) => { + return ( +
+ {typeParam} +
+ ); + })} +
+
+ )} + {Params.length > 0 && ( +
+
Parameters
+ + {...Params} +
+ )} +
+
Returns
+ {returnDocComment &&
{returnDocComment}
} +
+ {(returnTypeRenderer && ( + + )) ||
{returnType.toString()}
} +
+
+
+ ); +} diff --git a/components/renderers/function/index.ts b/components/renderers/function/index.ts new file mode 100644 index 0000000..19edf9e --- /dev/null +++ b/components/renderers/function/index.ts @@ -0,0 +1 @@ +export { functionRenderer } from './function.renderer'; diff --git a/components/renderers/grouped-schema-nodes-summary/grouped-schema-nodes-summary.module.scss b/components/renderers/grouped-schema-nodes-summary/grouped-schema-nodes-summary.module.scss new file mode 100644 index 0000000..1bb84cf --- /dev/null +++ b/components/renderers/grouped-schema-nodes-summary/grouped-schema-nodes-summary.module.scss @@ -0,0 +1,57 @@ +.groupName { + font-size: var(--bit-p-lg); + font-weight: 500; + text-transform: capitalize; + padding-bottom: 16px; +} + +.groupNodesContainer { + display: flex; + flex-direction: column; +} + +.table { + display: flex; + flex-direction: column; +} + +.paddingTop { + padding-top: 20px; +} + +.row { + display: flex; + justify-content: space-between; + overflow-wrap: break-word; +} + +.headingRow { + padding: 8px 0px 16px 0px; +} + +.row > div { + flex: 1; + text-align: left; +} + +.name { + word-break: normal; +} + +.codeSnippet { + font-size: var(--bit-p-xs); + word-break: normal; + position: relative; + line-height: 1.2em; + background-color: var(--surface01-color, #fffbff); + padding: 4px; + border-radius: 4px; + word-break: keep-all; + flex-wrap: wrap; +} + +.table { + display: flex; + flex: 1; + padding-top: 8px; +} diff --git a/components/renderers/grouped-schema-nodes-summary/grouped-schema-nodes-summary.tsx b/components/renderers/grouped-schema-nodes-summary/grouped-schema-nodes-summary.tsx new file mode 100644 index 0000000..094e9eb --- /dev/null +++ b/components/renderers/grouped-schema-nodes-summary/grouped-schema-nodes-summary.tsx @@ -0,0 +1,152 @@ +import type { HTMLAttributes } from 'react'; +import React from 'react'; +import type { SchemaNode, EnumMemberSchema } from '@teambit/semantics.entities.semantic-schema'; +import { + groupByNodeSignatureType, + sortSignatureType, +} from '@teambit/api-reference.utils.group-schema-node-by-signature'; +import { HeadingRow } from '@teambit/documenter.ui.table-heading-row'; +import type { APINodeRenderProps } from '@teambit/api-reference.models.api-node-renderer'; +import { + FunctionNodeSummary, + VariableNodeSummary, + trackedElementClassName, + EnumMemberSummary, +} from '@teambit/api-reference.renderers.schema-node-member-summary'; +import classnames from 'classnames'; + +import styles from './grouped-schema-nodes-summary.module.scss'; + +export type GroupedSchemaNodesSummaryProps = { + nodes: SchemaNode[]; + apiNodeRendererProps: APINodeRenderProps; + headings?: Record; + skipGrouping?: boolean; + skipNode?(type: string, members: SchemaNode[]): boolean; + renderTable?(type: string, member: SchemaNode, headings?: string[]): React.ReactNode; +} & HTMLAttributes; + +const DEFAULT_HEADINGS = { + methods: ['name', 'signature', 'description'], + constructors: ['name', 'signature', 'description'], + 'enum members': ['name', 'description'], + properties: ['name', 'type', 'default', 'description'], + setters: ['name', 'signature', 'description'], + default: ['name', 'type', 'description'], +}; + +const defaultTableRenderer = function DefaultTableRendererWrapper(apiNodeRendererProps: APINodeRenderProps) { + return function DefaultTableRenderer(type: string, member: SchemaNode, headings: string[]) { + const typeId = type && encodeURIComponent(type); + + if (type === 'enum members') { + return ( + + ); + } + + return ( + + ); + }; +}; + +export function GroupedSchemaNodesSummary({ + nodes, + apiNodeRendererProps, + headings: headingsFromProps = {}, + skipNode, + skipGrouping, + renderTable = defaultTableRenderer(apiNodeRendererProps), + className, + ...rest +}: GroupedSchemaNodesSummaryProps) { + const hasNodes = nodes.length > 0; + const headings = { + ...DEFAULT_HEADINGS, + ...headingsFromProps, + }; + + const groupedNodes = + !skipGrouping && hasNodes + ? Array.from(groupByNodeSignatureType(nodes).entries()).sort(sortSignatureType) + : (hasNodes && [['', nodes] as [string, SchemaNode[]]]) || []; + + const { apiRefModel } = apiNodeRendererProps; + + return ( +
+ {groupedNodes.map(([type, groupedMembersByType], index) => { + const skip = skipNode && type && skipNode(type, groupedMembersByType as any); + const skipRenderingTable = type === 'methods' || type === 'constructors' || type === 'setters'; + const typeId = type && encodeURIComponent(type); + const _headings = (type && headings[type]) || headings.default; + + if (skip) return null; + + return ( +
+ {type && ( +
+ {type} +
+ )} + {!skipRenderingTable && ( +
+ + {groupedMembersByType.map((member) => { + return renderTable(type ?? '', member as any, _headings); + })} +
+ )} + {skipRenderingTable && + groupedMembersByType.map((member) => { + if (!type) return null; + const params = + (member as any).params || + (member as any).props || + ((member as any).param && [(member as any).param]) || + []; + + return ( + + ); + })} +
+ ); + })} +
+ ); +} diff --git a/components/renderers/grouped-schema-nodes-summary/index.ts b/components/renderers/grouped-schema-nodes-summary/index.ts new file mode 100644 index 0000000..3aa19cb --- /dev/null +++ b/components/renderers/grouped-schema-nodes-summary/index.ts @@ -0,0 +1 @@ +export { GroupedSchemaNodesSummaryProps, GroupedSchemaNodesSummary } from './grouped-schema-nodes-summary'; diff --git a/components/renderers/inference-type/index.ts b/components/renderers/inference-type/index.ts new file mode 100644 index 0000000..7a4f06d --- /dev/null +++ b/components/renderers/inference-type/index.ts @@ -0,0 +1 @@ +export { inferenceTypeRenderer } from './inference-type.renderer'; diff --git a/components/renderers/inference-type/inference-type.module.scss b/components/renderers/inference-type/inference-type.module.scss new file mode 100644 index 0000000..51e73ff --- /dev/null +++ b/components/renderers/inference-type/inference-type.module.scss @@ -0,0 +1,37 @@ +.inferenceWrapper { + position: relative; + + > pre { + // override syntax highlighter inline styles so the type box is theme-aware + background: var(--surface01-color, #f1f2f4) !important; + color: var(--bit-accent-text, #6c5ce7) !important; + + > code { + // override syntax highlighter code font size + force inherit so default text follows the theme + font-size: var(--bit-p-xxs, 12px) !important; + background: transparent !important; + color: inherit !important; + } + } +} + +.isSpread { + > span { + font-style: italic; + } +} + +.syntaxTooltip { + background-color: var(--surface01-color, rgb(241, 242, 244)); +} + +.infoIcon { + font-style: normal; + padding-left: 4px; +} + +.infoIconWrapper { + display: flex; + align-items: center; + justify-content: center; +} diff --git a/components/renderers/inference-type/inference-type.renderer.tsx b/components/renderers/inference-type/inference-type.renderer.tsx new file mode 100644 index 0000000..3d82e37 --- /dev/null +++ b/components/renderers/inference-type/inference-type.renderer.tsx @@ -0,0 +1,95 @@ +import React from 'react'; +import type { APINodeRenderProps, APINodeRenderer } from '@teambit/api-reference.models.api-node-renderer'; +import { nodeStyles } from '@teambit/api-reference.models.api-node-renderer'; +import { InferenceTypeSchema } from '@teambit/semantics.entities.semantic-schema'; +import SyntaxHighlighter from 'react-syntax-highlighter/dist/esm/prism-light'; +import tsxSyntax from 'react-syntax-highlighter/dist/esm/languages/prism/tsx'; +import defaultTheme from '@teambit/api-reference.utils.custom-prism-syntax-highlighter-theme'; +import classNames from 'classnames'; +import { Icon } from '@teambit/evangelist.elements.icon'; +import { Tooltip } from '@teambit/design.ui.tooltip'; + +import styles from './inference-type.module.scss'; + +SyntaxHighlighter.registerLanguage('tsx', tsxSyntax); + +export const inferenceTypeRenderer: APINodeRenderer = { + predicate: (node) => node.__schema === InferenceTypeSchema.name, + Component: InferenceTypeComponent, + nodeType: 'InferenceType', + default: true, +}; + +function InferenceTypeComponent(props: APINodeRenderProps) { + const { + apiNode: { api }, + className, + metadata, + } = props; + + const inferenceTypeNode = api as InferenceTypeSchema; + const isSpread = inferenceTypeNode.isSpread; + const typeToRender = (inferenceTypeNode.type || inferenceTypeNode.name) ?? ''; + + const maybeObject = typeToRender.includes('{'); + const disableHighlight = metadata?.[inferenceTypeNode.__schema]?.disableHighlight || !maybeObject; + + const lang = React.useMemo(() => { + const langFromFileEnding = api.location.filePath?.split('.').pop(); + if (langFromFileEnding === 'scss' || langFromFileEnding === 'sass') return 'css'; + if (langFromFileEnding === 'mdx') return 'md'; + return langFromFileEnding; + }, [api.location.filePath]); + + return ( +
+ {isSpread && ( + + {typeToRender} + + } + > +
+ {'...rest'} + +
+
+ )} + {!isSpread && + (!disableHighlight ? ( + + {typeToRender} + + ) : ( + inferenceTypeNode.type || inferenceTypeNode.name + ))} +
+ ); +} diff --git a/components/renderers/interface/index.ts b/components/renderers/interface/index.ts new file mode 100644 index 0000000..17e697e --- /dev/null +++ b/components/renderers/interface/index.ts @@ -0,0 +1 @@ +export { interfaceRenderer } from './interface.renderer'; diff --git a/components/renderers/interface/interface.renderer.module.scss b/components/renderers/interface/interface.renderer.module.scss new file mode 100644 index 0000000..e69de29 diff --git a/components/renderers/interface/interface.renderer.tsx b/components/renderers/interface/interface.renderer.tsx new file mode 100644 index 0000000..88ca213 --- /dev/null +++ b/components/renderers/interface/interface.renderer.tsx @@ -0,0 +1,53 @@ +import React from 'react'; +import { InterfaceSchema } from '@teambit/semantics.entities.semantic-schema'; +import type { APINodeRenderProps, APINodeRenderer } from '@teambit/api-reference.models.api-node-renderer'; +import { APINodeDetails } from '@teambit/api-reference.renderers.api-node-details'; +import { GroupedSchemaNodesSummary } from '@teambit/api-reference.renderers.grouped-schema-nodes-summary'; +import { SchemaNodesSummary } from '@teambit/api-reference.overview.renderers.grouped-schema-nodes-overview-summary'; + +export const interfaceRenderer: APINodeRenderer = { + predicate: (node) => node.__schema === InterfaceSchema.name, + Component: InterfaceComponent, + OverviewComponent: InterfaceOverviewComponent, + nodeType: 'Interfaces', + icon: { name: 'Interface', url: 'https://static.bit.dev/api-reference/interface.svg' }, + default: true, +}; + +function InterfaceComponent(props: APINodeRenderProps) { + const { + apiNode: { api }, + } = props; + const interfaceNode = api as InterfaceSchema; + const { extendsNodes, signature, members } = interfaceNode; + const extendsSignature = extendsNodes?.[0]?.name; + const displaySignature = `${signature}${(extendsSignature && ' '.concat(extendsSignature)) || ''}`; + + return ( + + + + ); +} + +function InterfaceOverviewComponent(props: APINodeRenderProps) { + const { + apiNode: { api, renderer }, + } = props; + const interfaceNode = api as InterfaceSchema; + const { members, doc } = interfaceNode; + + const icon = renderer.icon; + const description = + doc?.comment ?? doc?.tags?.filter((tag) => tag.comment).reduce((acc, tag) => acc.concat(`${tag.comment}\n`), ''); + + return ( + + ); +} diff --git a/components/renderers/parameter/index.ts b/components/renderers/parameter/index.ts new file mode 100644 index 0000000..acf088d --- /dev/null +++ b/components/renderers/parameter/index.ts @@ -0,0 +1 @@ +export { parameterRenderer } from './parameter.renderer'; diff --git a/components/renderers/parameter/parameter.renderer.module.scss b/components/renderers/parameter/parameter.renderer.module.scss new file mode 100644 index 0000000..05737a4 --- /dev/null +++ b/components/renderers/parameter/parameter.renderer.module.scss @@ -0,0 +1,58 @@ +.parameterContainer, +.parameterName, +.parameterType { + display: flex; +} + +.parameterContainer { + padding-bottom: 16px; +} + +.parameterName { + padding-right: 8px; +} + +.node { + font-size: var(--bit-p-xs); + word-break: normal; + position: relative; + line-height: 1.2em; + // background-color: var(--surface01-color, #fffbff); + padding: 4px; + border-radius: 4px; + word-break: keep-all; + flex-wrap: wrap; +} + +.paramType { + display: flex; + flex-direction: row; + gap: 4px; + justify-content: center; + align-items: center; + width: fit-content; + font-size: var(--bit-p-xs); + + .name { + display: flex; + + &:after { + content: ':'; + padding: 0px 1px; + font-weight: 500; + } + } + + .type { + display: flex; + } +} + +.paramRow { + padding: 8px 0px !important; + margin-bottom: 0; +} + +.paramHeading { + padding: 8px 0px 16px 0px; +} diff --git a/components/renderers/parameter/parameter.renderer.tsx b/components/renderers/parameter/parameter.renderer.tsx new file mode 100644 index 0000000..b9684bd --- /dev/null +++ b/components/renderers/parameter/parameter.renderer.tsx @@ -0,0 +1,157 @@ +import React from 'react'; +import type { APINodeRenderProps, APINodeRenderer } from '@teambit/api-reference.models.api-node-renderer'; +import { nodeStyles } from '@teambit/api-reference.models.api-node-renderer'; +import type { InferenceTypeSchema } from '@teambit/semantics.entities.semantic-schema'; +import { ParameterSchema, TypeRefSchema } from '@teambit/semantics.entities.semantic-schema'; +import { TableRow, DefaultValueCell } from '@teambit/documenter.ui.table-row'; +import { HeadingRow } from '@teambit/documenter.ui.table-heading-row'; + +import styles from './parameter.renderer.module.scss'; + +export const parameterRenderer: APINodeRenderer = { + predicate: (node) => node.__schema === ParameterSchema.name, + Component: ParameterComponent, + nodeType: 'Parameters', + default: true, +}; + +function ParameterComponent(props: APINodeRenderProps) { + const { + apiNode: { api }, + apiRefModel, + renderers, + depth = 0, + metadata, + } = props; + const paramNode = api as ParameterSchema; + const skipHeadings = metadata?.[paramNode.__schema]?.skipHeadings; + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { name, type, objectBindingNodes } = paramNode; + const typeRenderer = renderers.find((renderer) => renderer.predicate(type)); + const typeRef = type.name + ? apiRefModel.apiByName.get(type.name) || + apiRefModel.apiByName.get(apiRefModel.generateInternalAPIKey(type.location.filePath, type.name)) + : undefined; + const headings = ['name', 'type', 'default', 'description']; + + const ObjectBindingNodeComponent = + objectBindingNodes && objectBindingNodes.length > 0 ? ( + + {!skipHeadings && } + {objectBindingNodes.map((_bindingNode) => { + const typeRefCorrespondingNode = typeRef?.api.findNode((node) => { + const matchesName = node.name === _bindingNode.name; + const matchesAlias = (_bindingNode as any).alias && node.name === (_bindingNode as any).alias; + return matchesName || matchesAlias; + }); + + const isTypeRefCorrespondingNodeReference = + (typeRefCorrespondingNode as any)?.type?.__schema === TypeRefSchema.name; + + const typeRefBindingNode = isTypeRefCorrespondingNodeReference + ? ((typeRefCorrespondingNode as any).type as TypeRefSchema) + : undefined; + + // @todo - temp fix. This should be fixed at the schema extractor level + const doesTypeHaveIncorrectName = typeRefBindingNode && typeRefBindingNode.name !== _bindingNode.name; + + const bindingNode = isTypeRefCorrespondingNodeReference + ? (typeRefCorrespondingNode as any).type + : _bindingNode; + + const bindingNodeRenderer = renderers.find((renderer) => renderer.predicate(bindingNode)); + + const customBindingNodeTypeRow = (bindingNodeRenderer && ( + + )) ||
{bindingNode.toString()}
; + + return ( + + ) : undefined, + }} + className={styles.paramRow} + row={{ + name: doesTypeHaveIncorrectName ? _bindingNode.name : bindingNode.name || '', + description: bindingNode.doc?.comment || bindingNode.doc?.raw || bindingNode.doc?.tags?.join() || '', + required: + (typeRefCorrespondingNode as any)?.isOptional !== undefined && + !(typeRefCorrespondingNode as any)?.isOptional, + type: '', + // infer defaultValue + default: { + value: (_bindingNode as InferenceTypeSchema).defaultValue || '', + }, + }} + /> + ); + })} +
+ ) : null; + + const ParameterTypeRefRender = typeRef && renderers.find((renderer) => renderer.predicate(typeRef.api)); + + const ParameterTypeComponent = + depth < 1 && ParameterTypeRefRender ? ( + + ) : ( + (typeRenderer && ( + + )) ||
{type.toString()}
+ ); + + const ParameterTypeTable = () => { + const paramTypeHeadings = ['name', 'type', 'default', 'description']; + + return ( + + {!skipHeadings && } + : undefined, + }} + row={{ + name: paramNode.name || '', + description: paramNode.doc?.comment || paramNode.doc?.raw || paramNode.doc?.tags?.join() || '', + required: paramNode.isOptional !== undefined && !paramNode.isOptional, + type: '', + default: { + value: paramNode.defaultValue || '', + }, + }} + /> + + ); + }; + + return <>{ObjectBindingNodeComponent || }; +} diff --git a/components/renderers/react/index.ts b/components/renderers/react/index.ts new file mode 100644 index 0000000..5219e4c --- /dev/null +++ b/components/renderers/react/index.ts @@ -0,0 +1 @@ +export { reactRenderer } from './react.renderer'; diff --git a/components/renderers/react/react.renderer.module.scss b/components/renderers/react/react.renderer.module.scss new file mode 100644 index 0000000..7c2378a --- /dev/null +++ b/components/renderers/react/react.renderer.module.scss @@ -0,0 +1,98 @@ +.container { + display: flex; + flex-direction: column; +} + +.title { + padding-bottom: 16px; + font-size: var(--bit-p-lg); + font-weight: 600; +} + +.value { + font-size: var(--bit-p-xs); + background: var(--bit-bg-dent); + padding: 16px; + border-radius: 6px; + font-family: Roboto mono; +} + +.topPad { + padding: 16px 0px; + border-top: 1px solid var(--border-medium-color); +} + +.typeParams { + padding-top: 16px; + border-top: 1px solid var(--border-medium-color); +} + +.bold { + font-weight: 500; +} + +.table { + padding: 16px 8px; + + > div { + grid-gap: 16px 24px; + } +} + +.returnType { + font-size: var(--bit-p-xxs); + // color: #397300; + display: flex; + color: #4646c6; +} + +.node { + display: flex; + font-size: var(--bit-p-xxs); + word-break: normal; +} + +.paramRef { + padding-bottom: 16px; + font-size: var(--bit-p-md); +} + +.docComment { + font-size: var(--bit-p-xs); + padding-bottom: 16px; +} + +.reactOverview { + display: flex; + flex-direction: column; + background-color: rgba(247, 247, 245, 0.631372549); + min-width: fit-content; + max-width: 75%; + gap: 8px; + padding: 16px; + border-radius: 8px; +} + +.reactOverviewHeader { + display: flex; + align-items: center; +} + +.headingLeft { + display: flex; + gap: 4px; + width: 240px; + text-overflow: ellipsis; +} + +.icon { + > img { + width: 18px; + height: 18px; + } +} + +.title { + font-size: 18px; + font-weight: 600; +} diff --git a/components/renderers/react/react.renderer.tsx b/components/renderers/react/react.renderer.tsx new file mode 100644 index 0000000..9048fd1 --- /dev/null +++ b/components/renderers/react/react.renderer.tsx @@ -0,0 +1,205 @@ +import React from 'react'; +import type { ReactSchema } from '@teambit/react'; +import type { APINodeRenderProps, APINodeRenderer } from '@teambit/api-reference.models.api-node-renderer'; +import { nodeStyles } from '@teambit/api-reference.models.api-node-renderer'; +import { APINodeDetails } from '@teambit/api-reference.renderers.api-node-details'; +import { parameterRenderer as defaultParamRenderer } from '@teambit/api-reference.renderers.parameter'; +import classnames from 'classnames'; +import { TagName } from '@teambit/semantics.entities.semantic-schema'; +import { Link as BaseLink } from '@teambit/base-react.navigation.link'; +// @todo - this will be fixed as part of the @teambit/base-react.navigation.link upgrade to latest +const Link = BaseLink as any; + +import styles from './react.renderer.module.scss'; + +export const reactRenderer: APINodeRenderer = { + predicate: (node) => node.__schema === 'ReactSchema', + Component: ReactComponent, + OverviewComponent: ReactOverviewComponent, + nodeType: 'React', + icon: { name: 'React', url: 'https://static.bit.dev/extensions-icons/react.svg' }, + default: true, +}; + +function ReactComponent(props: APINodeRenderProps) { + const { + apiNode: { api }, + renderers, + // apiRefModel, + } = props; + const reactNode = api as ReactSchema; + const { returnType, props: reactProps, typeParams } = reactNode; + const returnTypeRenderer = renderers.find((renderer) => renderer.predicate(returnType)); + + if (props.metadata?.[api.__schema]?.columnView) { + return
{api.toString()}
; + } + + const paramRenderer = reactProps && renderers.find((renderer) => renderer.predicate(reactProps)); + const paramRef = reactProps?.type; + const paramRefRenderer = paramRef && renderers.find((renderer) => renderer.predicate(paramRef)); + const PropsRefComponent = + paramRef && paramRefRenderer?.Component ? ( + + ) : null; + + const ParamComponent = + reactProps && paramRenderer?.Component ? ( + + ) : ( + (reactProps && ( + + )) || + null + ); + + const docComment = api.doc?.findTag(TagName.return)?.comment; + + return ( + + {typeParams && ( +
+
Type Parameters
+
+ {typeParams.map((typeParam) => { + return ( +
+ {typeParam} +
+ ); + })} +
+
+ )} + {ParamComponent && ( +
+
Props
+
{PropsRefComponent}
+ {ParamComponent} +
+ )} +
+
Returns
+ {docComment &&
{docComment}
} +
+ {(returnTypeRenderer && ( + + )) ||
{returnType.toString()}
} +
+
+
+ ); +} + +function ReactOverviewComponent(props: APINodeRenderProps) { + const { + apiNode: { api }, + renderers, + // apiRefModel, + } = props; + const reactNode = api as ReactSchema; + const { returnType, props: reactProps } = reactNode; + const returnTypeRenderer = renderers.find((renderer) => renderer.predicate(returnType)); + + const paramRenderer = reactProps && renderers.find((renderer) => renderer.predicate(reactProps)); + const paramRef = reactProps?.type; + const paramRefRenderer = paramRef && renderers.find((renderer) => renderer.predicate(paramRef)); + const PropsRefComponent = + paramRef && paramRefRenderer?.Component ? ( + + ) : null; + + const ParamComponent = + reactProps && paramRenderer?.Component ? ( + + ) : ( + (reactProps && ( + + )) || + null + ); + + const docComment = api.doc?.findTag(TagName.return)?.comment; + const icon = reactRenderer.icon; + const description = + api.doc?.comment ?? + api?.doc?.tags?.filter((tag) => tag.comment).reduce((acc, tag) => acc.concat(`${tag.comment}\n`), ''); + + return ( +
+
+
+ {icon && ( +
+ {icon.name} +
+ )} +
+ {api.name} +
+
+
+ {description &&
{description}
} + {ParamComponent && ( +
+
Props
+
{PropsRefComponent}
+ {ParamComponent} +
+ )} +
+
Returns
+ {docComment &&
{docComment}
} +
+ {(returnTypeRenderer && ( + + )) ||
{returnType.toString()}
} +
+
+
+ ); +} diff --git a/components/renderers/schema-node-member-summary/enum-member-summary.module.scss b/components/renderers/schema-node-member-summary/enum-member-summary.module.scss new file mode 100644 index 0000000..b73e0b9 --- /dev/null +++ b/components/renderers/schema-node-member-summary/enum-member-summary.module.scss @@ -0,0 +1,16 @@ +.signature { + display: flex; + margin-right: 5px; + font-family: Roboto mono; + font-size: var(--bit-p-xxs); + word-break: normal; + color: rgb(57, 115, 0); +} + +.name { + word-break: normal; +} + +.row { + grid-template-columns: 1fr 2fr 1fr; +} diff --git a/components/renderers/schema-node-member-summary/enum-member-summary.tsx b/components/renderers/schema-node-member-summary/enum-member-summary.tsx new file mode 100644 index 0000000..c59f6a0 --- /dev/null +++ b/components/renderers/schema-node-member-summary/enum-member-summary.tsx @@ -0,0 +1,59 @@ +import type { HTMLAttributes } from 'react'; +import React from 'react'; +import type { EnumMemberSchema } from '@teambit/semantics.entities.semantic-schema'; +import { TableRow } from '@teambit/documenter.ui.table-row'; +import classnames from 'classnames'; +import type { APINodeRenderProps } from '@teambit/api-reference.models.api-node-renderer'; +import { transformSignature } from '@teambit/api-reference.utils.schema-node-signature-transform'; +import { trackedElementClassName } from './index'; + +import styles from './enum-member-summary.module.scss'; + +export type EnumMemberSummaryProps = { + groupElementClassName?: string; + node: EnumMemberSchema; + headings: string[]; + name: string; + apiNodeRendererProps: APINodeRenderProps; +} & HTMLAttributes; + +/** + * @todo handle doc.tags + */ +export function EnumMemberSummary({ + groupElementClassName, + className, + headings, + node, + name, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + apiNodeRendererProps, + ...rest +}: EnumMemberSummaryProps) { + const { __schema, doc } = node; + const signature = transformSignature(node); + + return ( + + {name} + + ), + signature:
{signature}
, + }} + row={{ + name, + description: doc?.comment || '', + required: false, + type: '', + }} + /> + ); +} diff --git a/components/renderers/schema-node-member-summary/function-node-summary.module.scss b/components/renderers/schema-node-member-summary/function-node-summary.module.scss new file mode 100644 index 0000000..e46a098 --- /dev/null +++ b/components/renderers/schema-node-member-summary/function-node-summary.module.scss @@ -0,0 +1,66 @@ +.summaryContainer { + padding: 0px 24px; + margin-bottom: 16px; + border-radius: 8px; + background-color: var(--surface01-color, #f7f7f5a1); + display: flex; + flex-direction: column; + gap: 16px; +} + +.signatureTitle { + color: var(--on-background-color); + + > pre { + // override syntax highlighter inline styles so the signature box is theme-aware + background: var(--surface01-color, #f1f2f4) !important; + color: var(--bit-accent-text, #6c5ce7) !important; + + > code { + // override syntax highlighter code font size + force inherit so default text follows the theme + font-size: var(--bit-p-xxs, 12px) !important; + background: transparent !important; + color: inherit !important; + } + } +} + +.description { + font-size: 14px; + margin-bottom: 16px; + font-style: italic; +} + +.subtitle { + font-weight: 500; + text-transform: uppercase; + font-size: var(--bit-p-xxs); + padding-right: 16px; + margin-bottom: 12px; +} + +.returnContainer { + margin-bottom: 16px; +} + +.functionName { + font-size: var(--bit-p-md); + font-weight: 500; + padding: 16px 0px 8px 0px; +} + +.hide { + padding: 8px 0px !important; +} + +.paramHeading { + padding: 8px 0px 16px 0px; +} +.apiNodeDetailsExample { + padding-bottom: 24px; +} +.apiNodeDetailsExampleTitle { + padding: 16px 0px; + font-weight: bold; + font-size: var(--bit-p-xs, '14px'); +} diff --git a/components/renderers/schema-node-member-summary/function-node-summary.tsx b/components/renderers/schema-node-member-summary/function-node-summary.tsx new file mode 100644 index 0000000..77b9b1f --- /dev/null +++ b/components/renderers/schema-node-member-summary/function-node-summary.tsx @@ -0,0 +1,138 @@ +import type { HTMLAttributes } from 'react'; +import React, { useMemo } from 'react'; +import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; +import type { SchemaNode } from '@teambit/semantics.entities.semantic-schema'; +import { SetAccessorSchema } from '@teambit/semantics.entities.semantic-schema'; +import { transformSignature } from '@teambit/api-reference.utils.schema-node-signature-transform'; +import type { APIReferenceModel } from '@teambit/api-reference.models.api-reference-model'; +import type { APINodeRenderProps } from '@teambit/api-reference.models.api-node-renderer'; +import { nodeStyles } from '@teambit/api-reference.models.api-node-renderer'; +import { parameterRenderer as defaultParamRenderer } from '@teambit/api-reference.renderers.parameter'; +import { HeadingRow } from '@teambit/documenter.ui.table-heading-row'; +import defaultTheme from '@teambit/api-reference.utils.custom-prism-syntax-highlighter-theme'; +import { extractCodeBlock } from '@teambit/api-reference.renderers.api-node-details'; +import classNames from 'classnames'; + +import styles from './function-node-summary.module.scss'; + +export type FunctionNodeSummaryProps = { + groupElementClassName?: string; + node: SchemaNode; + name: string; + hideName?: boolean; + headings: string[]; + apiRefModel: APIReferenceModel; + returnType?: SchemaNode; + params: SchemaNode[]; + apiNodeRendererProps: APINodeRenderProps; +} & HTMLAttributes; + +export function FunctionNodeSummary({ + node, + name, + params, + returnType, + apiNodeRendererProps, + hideName, +}: FunctionNodeSummaryProps) { + const { + __schema, + doc, + location: { filePath }, + } = node; + const signature = + __schema === SetAccessorSchema.name + ? `(${(node as SetAccessorSchema).param.toString()}) => void` + : (transformSignature(node)?.split(name)[1] ?? node.signature); + + const { renderers } = apiNodeRendererProps; + const returnTypeRenderer = returnType && renderers.find((renderer) => renderer.predicate(returnType)); + const lang = useMemo(() => { + const langFromFileEnding = filePath?.split('.').pop(); + if (langFromFileEnding === 'scss' || langFromFileEnding === 'sass') return 'css'; + if (langFromFileEnding === 'mdx') return 'md'; + return langFromFileEnding; + }, [filePath]); + const paramTypeHeadings = ['Parameter', 'type', 'default', 'description']; + const docs = doc?.comment || doc?.raw; + const example = (doc?.tags || []).find((tag) => tag.tagName === 'example'); + + return ( +
+
+ {
{hideName ? '' : name}
} + {docs &&
{docs || ''}
} + {signature && ( + + {signature} + + )} + {example && example.comment && ( +
+
Example
+ + {extractCodeBlock(example.comment)?.code || example.comment} + +
+ )} +
+ {params.length > 0 && ( +
+ + {params.map((param) => { + const paramRenderer = renderers.find((renderer) => renderer.predicate(param)); + if (paramRenderer?.Component) { + return ( + + ); + } + return ( + + ); + })} +
+ )} + {returnType && ( +
+

Returns

+ {(returnTypeRenderer && ( + + )) ||
{returnType.toString()}
} +
+ )} +
+ ); +} diff --git a/components/renderers/schema-node-member-summary/index.ts b/components/renderers/schema-node-member-summary/index.ts new file mode 100644 index 0000000..0b0bf55 --- /dev/null +++ b/components/renderers/schema-node-member-summary/index.ts @@ -0,0 +1,4 @@ +export { EnumMemberSummaryProps, EnumMemberSummary } from './enum-member-summary'; +export { VariableNodeSummary, VariableNodeSummaryProps } from './variable-node-summary'; +export { FunctionNodeSummary, FunctionNodeSummaryProps } from './function-node-summary'; +export const trackedElementClassName = 'tracked-element'; diff --git a/components/renderers/schema-node-member-summary/variable-node-summary.module.scss b/components/renderers/schema-node-member-summary/variable-node-summary.module.scss new file mode 100644 index 0000000..2b620c3 --- /dev/null +++ b/components/renderers/schema-node-member-summary/variable-node-summary.module.scss @@ -0,0 +1,20 @@ +.name { + word-break: normal; +} + +.node { + font-size: var(--bit-p-xs); + word-break: normal; + position: relative; + line-height: 1.2em; + background-color: var(--surface01-color, #fffbff); + padding: 4px; + border-radius: 4px; + word-break: keep-all; + flex-wrap: wrap; +} + +.row { + padding: 8px 0px !important; + margin-bottom: 0; +} diff --git a/components/renderers/schema-node-member-summary/variable-node-summary.tsx b/components/renderers/schema-node-member-summary/variable-node-summary.tsx new file mode 100644 index 0000000..2cdb235 --- /dev/null +++ b/components/renderers/schema-node-member-summary/variable-node-summary.tsx @@ -0,0 +1,82 @@ +import type { HTMLAttributes } from 'react'; +import React from 'react'; +import type { IndexSignatureSchema, SchemaNode } from '@teambit/semantics.entities.semantic-schema'; +import { TableRow, DefaultValueCell } from '@teambit/documenter.ui.table-row'; +import classnames from 'classnames'; +import type { APINodeRenderProps } from '@teambit/api-reference.models.api-node-renderer'; +import { nodeStyles } from '@teambit/api-reference.models.api-node-renderer'; +import { trackedElementClassName } from './index'; + +import styles from './variable-node-summary.module.scss'; + +export type VariableNodeSummaryProps = { + groupElementClassName?: string; + node: SchemaNode; + headings: string[]; + name: string; + type?: SchemaNode; + isOptional?: boolean; + apiNodeRendererProps: APINodeRenderProps; + defaultValue?: string; +} & HTMLAttributes; + +/** + * @todo handle doc.tags + */ +export function VariableNodeSummary({ + groupElementClassName, + className, + headings, + node, + name, + isOptional, + type, + apiNodeRendererProps, + defaultValue, + ...rest +}: VariableNodeSummaryProps) { + const { __schema, doc } = node; + const { renderers } = apiNodeRendererProps; + const sanitizedName = __schema === 'IndexSignatureSchema' ? (node as IndexSignatureSchema).keyType.toString() : name; + const sanitizedType = __schema === 'IndexSignatureSchema' ? (node as IndexSignatureSchema).valueType : type; + const typeRenderer = sanitizedType && renderers.find((renderer) => renderer.predicate(sanitizedType)); + + const customTypeRow = (typeRenderer && ( + + )) ||
{type?.toString()}
; + + return ( + + {sanitizedName} + + ), + type: customTypeRow, + default: defaultValue ? : undefined, + }} + row={{ + name: sanitizedName, + description: doc?.comment || doc?.tags?.join() || '', + required: isOptional !== undefined && !isOptional, + type: '', + default: + (defaultValue && { + value: defaultValue, + }) || + undefined, + }} + /> + ); +} diff --git a/components/renderers/schema-nodes-index/index.ts b/components/renderers/schema-nodes-index/index.ts new file mode 100644 index 0000000..d6472ba --- /dev/null +++ b/components/renderers/schema-nodes-index/index.ts @@ -0,0 +1 @@ +export { SchemaNodesIndex, SchemaNodesIndexProps } from './schema-nodes-index'; diff --git a/components/renderers/schema-nodes-index/schema-nodes-index.module.scss b/components/renderers/schema-nodes-index/schema-nodes-index.module.scss new file mode 100644 index 0000000..44f1932 --- /dev/null +++ b/components/renderers/schema-nodes-index/schema-nodes-index.module.scss @@ -0,0 +1,56 @@ +.schemaNodeIndexContainer { + display: flex; + flex-direction: column; + overflow-y: auto; + min-width: 150px; + max-width: 20%; + height: 100%; +} + +.groupName { + display: flex; + font-weight: 500; + // padding-bottom: 8px; + font-size: var(--bit-p-sm); + text-transform: capitalize; +} + +.groupedNodesContainer { + display: flex; + flex-direction: column; + padding-bottom: 8px; + width: 100%; + flex-wrap: wrap; +} + +.group { + padding-right: 16px; + display: flex; + flex-direction: column; + border-left: 1px solid var(--bit-border-color-lightest, #ededed); +} +.firstNode { + margin-top: 16px; +} +.firstNodeLink { + padding-top: 0px; +} +.groupedNode { + display: flex; + font-size: var(--bit-p-xs); +} + +.noGroup { + padding-left: 0; +} + +.groupedNodeName { + border-radius: 4px; +} + +.title { + font-weight: 500; + font-size: var(--bit-p-xs); + text-transform: uppercase; + margin-bottom: 8px; +} diff --git a/components/renderers/schema-nodes-index/schema-nodes-index.tsx b/components/renderers/schema-nodes-index/schema-nodes-index.tsx new file mode 100644 index 0000000..108dbcc --- /dev/null +++ b/components/renderers/schema-nodes-index/schema-nodes-index.tsx @@ -0,0 +1,156 @@ +import type { HTMLAttributes } from 'react'; +import React, { useEffect, useRef, useMemo } from 'react'; +import { useElementOnFold } from '@teambit/docs.ui.hooks.use-element-on-fold'; +import classnames from 'classnames'; +import { classes } from '@teambit/design.ui.surfaces.menu.item'; +import flatten from 'lodash.flatten'; +import { trackedElementClassName } from '@teambit/api-reference.renderers.schema-node-member-summary'; +import { useLocation, Link as BaseLink } from '@teambit/base-react.navigation.link'; + +import styles from './schema-nodes-index.module.scss'; + +// @todo - this will be fixed as part of the @teambit/base-react.navigation.link upgrade to latest +const Link = BaseLink as any; + +export type SchemaNodesIndexProps = { + title?: string; + /** + * the ref of the parent element. fallback is document. + */ + rootRef?: React.MutableRefObject; +} & HTMLAttributes; + +export function SchemaNodesIndex({ rootRef, title, className, ...rest }: SchemaNodesIndexProps) { + const { elements } = useElementOnFold(rootRef, `.${trackedElementClassName}`); + const loadedRef = useRef(false); + const currentLocation = useLocation(); + const hash = currentLocation?.hash && decodeURIComponent(currentLocation.hash.slice(1)); + const grouped = useMemo(() => Array.from(groupElements(elements).entries()), [elements, elements.length]); + const hasGroupedElements = flatten(grouped).length > 0; + // scroll to active link, after first load and after content is loaded + useEffect(() => { + if (elements.length === 0) return; + if (loadedRef.current) return; + loadedRef.current = true; + + const _hash = currentLocation?.hash.slice(1); + if (!_hash) return; + + const matchingElement = elements.find((el) => el.id === decodeURIComponent(_hash)); + + if (matchingElement) matchingElement.scrollIntoView(); + }, [elements, elements.length]); + + if (!hasGroupedElements) return null; + + return ( +
+
{title}
+ {grouped.map(([group, groupedNodes], groupedIndex) => { + if (!group) { + return ( +
+ {groupedNodes.map((node, nodeIndex) => { + const isActive = node && decodeURIComponent(node) === hash; + return ( +
+ + {node} + +
+ ); + })} +
+ ); + } + return ( +
+
+ + {decodeURIComponent(group)} + +
+
+ {groupedNodes.map((node, nodeIndex) => { + const isActive = node && decodeURIComponent(node) === hash; + + return ( +
+ + {node} + +
+ ); + })} +
+
+ ); + })} +
+ ); +} + +function groupElements(elements: Element[]) { + const grouped = new Map(); + + let groupedElementId: string | null = null; + + for (let i = 0; i <= elements.length - 1; i += 1) { + const curr = elements[i]; + const next = elements[i + 1]; + const currDisplayId = curr.id; + // if the next element contains current elements id as class; start a new group + if (next && next.classList.contains(currDisplayId)) { + const groupId = currDisplayId; + groupedElementId = groupId; + grouped.set(groupedElementId, [next.textContent]); + i += 1; + } + + // if no groupedElementId; add it to no group + if (!groupedElementId) { + const existing = grouped.get(null) || []; + grouped.set(null, existing.concat([curr.textContent])); + } + + // if it is not a group node and groupElementId exists, add it to existing group + if (groupedElementId && currDisplayId !== groupedElementId) { + const existing = grouped.get(groupedElementId) || []; + grouped.set(groupedElementId, existing.concat(curr.textContent)); + } + } + + return grouped; +} diff --git a/components/renderers/this/index.ts b/components/renderers/this/index.ts new file mode 100644 index 0000000..3f26bac --- /dev/null +++ b/components/renderers/this/index.ts @@ -0,0 +1 @@ +export { thisRenderer } from './this.renderer'; diff --git a/components/renderers/this/this.renderer.tsx b/components/renderers/this/this.renderer.tsx new file mode 100644 index 0000000..c026cc9 --- /dev/null +++ b/components/renderers/this/this.renderer.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import type { APINodeRenderProps, APINodeRenderer } from '@teambit/api-reference.models.api-node-renderer'; +import { ThisTypeSchema } from '@teambit/semantics.entities.semantic-schema'; +import { TypeRefName } from '@teambit/api-reference.renderers.type-ref'; +import { useUpdatedUrlFromQuery } from '@teambit/api-reference.hooks.use-api-ref-url'; + +export const thisRenderer: APINodeRenderer = { + predicate: (node) => node.__schema === ThisTypeSchema.name, + Component: ThisComponent, + nodeType: 'This', + default: true, +}; + +function ThisComponent(props: APINodeRenderProps) { + const { + apiNode: { api }, + apiRefModel, + } = props; + + const thisType = api as ThisTypeSchema; + const typeRef = thisType.name !== 'this' ? apiRefModel.apiByName.get(thisType.name) : undefined; + + if (!typeRef) return <>{api.name}; + + return ( + + ); +} diff --git a/components/renderers/tuple-type/index.ts b/components/renderers/tuple-type/index.ts new file mode 100644 index 0000000..636bcde --- /dev/null +++ b/components/renderers/tuple-type/index.ts @@ -0,0 +1 @@ +export { tupleTypeRenderer } from './tuple-type.renderer'; diff --git a/components/renderers/tuple-type/tuple-type.renderer.module.scss b/components/renderers/tuple-type/tuple-type.renderer.module.scss new file mode 100644 index 0000000..ec8972e --- /dev/null +++ b/components/renderers/tuple-type/tuple-type.renderer.module.scss @@ -0,0 +1,25 @@ +.tupleArray { + display: inline; + + .bracketGroup { + display: inline-flex; + align-items: stretch; + } + + .bracket { + color: #666; + display: inline-flex; + align-items: center; + padding: 0 4px; + } + + .element { + display: inline-flex; + align-items: center; + } + + .comma { + color: #666; + margin-right: 4px; + } +} diff --git a/components/renderers/tuple-type/tuple-type.renderer.tsx b/components/renderers/tuple-type/tuple-type.renderer.tsx new file mode 100644 index 0000000..f7a62a9 --- /dev/null +++ b/components/renderers/tuple-type/tuple-type.renderer.tsx @@ -0,0 +1,69 @@ +import React from 'react'; +import type { APINodeRenderProps, APINodeRenderer } from '@teambit/api-reference.models.api-node-renderer'; +import { TupleTypeSchema } from '@teambit/semantics.entities.semantic-schema'; +import styles from './tuple-type.renderer.module.scss'; + +export const tupleTypeRenderer: APINodeRenderer = { + predicate: (node) => node.__schema === TupleTypeSchema.name, + Component: TupleTypeArray, + nodeType: 'TupleTypeArray', + default: true, +}; + +function TupleTypeArray(props: APINodeRenderProps) { + const { + apiNode: { api }, + renderers, + } = props; + + const tupleTypeArray = api as TupleTypeSchema; + + return ( +
+
+
[
+ {tupleTypeArray.elements[0] && ( +
+ + {tupleTypeArray.elements.length > 1 &&
,
} +
+ )} +
+ {tupleTypeArray.elements.slice(1, -1).map((element, index) => ( +
+ +
,
+
+ ))} + {tupleTypeArray.elements.length > 1 && ( +
+
+ +
+
]
+
+ )} + {tupleTypeArray.elements.length <= 1 &&
]
} +
+ ); +} + +function ElementRenderer({ element, ...props }) { + const { renderers } = props; + const elementRenderer = renderers.find((renderer) => renderer.predicate(element)); + if (elementRenderer) { + return ( + + ); + } + return element.toString(); +} diff --git a/components/renderers/type-array/index.ts b/components/renderers/type-array/index.ts new file mode 100644 index 0000000..085d704 --- /dev/null +++ b/components/renderers/type-array/index.ts @@ -0,0 +1 @@ +export { typeArrayRenderer } from './type-array.renderer'; diff --git a/components/renderers/type-array/type-array.renderer.module.scss b/components/renderers/type-array/type-array.renderer.module.scss new file mode 100644 index 0000000..a1d9d7c --- /dev/null +++ b/components/renderers/type-array/type-array.renderer.module.scss @@ -0,0 +1,5 @@ +.code { + font-family: 'Roboto Mono', monospace; + font-size: 12px; + line-height: 2; +} diff --git a/components/renderers/type-array/type-array.renderer.tsx b/components/renderers/type-array/type-array.renderer.tsx new file mode 100644 index 0000000..250affd --- /dev/null +++ b/components/renderers/type-array/type-array.renderer.tsx @@ -0,0 +1,42 @@ +import React from 'react'; +import type { APINodeRenderProps, APINodeRenderer } from '@teambit/api-reference.models.api-node-renderer'; +import { nodeStyles } from '@teambit/api-reference.models.api-node-renderer'; +import { TypeArraySchema } from '@teambit/semantics.entities.semantic-schema'; +import styles from './type-array.renderer.module.scss'; + +export const typeArrayRenderer: APINodeRenderer = { + predicate: (node) => node.__schema === TypeArraySchema.name, + Component: TypeArrayComponent, + nodeType: 'TypeArray', + default: true, +}; + +function TypeArrayComponent(props: APINodeRenderProps) { + const { + apiNode: { api }, + renderers, + } = props; + + const typeArray = api as TypeArraySchema; + const typeRenderer = renderers.find((renderer) => renderer.predicate(typeArray.type)); + + if (typeRenderer) { + return ( +
+ + {'[]'} +
+ ); + } + + return ( +
+ {typeArray.toString()} +
+ ); +} diff --git a/components/renderers/type-intersection/index.ts b/components/renderers/type-intersection/index.ts new file mode 100644 index 0000000..d4d36b2 --- /dev/null +++ b/components/renderers/type-intersection/index.ts @@ -0,0 +1 @@ +export { typeIntersectionRenderer } from './type-intersection.renderer'; diff --git a/components/renderers/type-intersection/type-intersection.renderer.module.scss b/components/renderers/type-intersection/type-intersection.renderer.module.scss new file mode 100644 index 0000000..264a65c --- /dev/null +++ b/components/renderers/type-intersection/type-intersection.renderer.module.scss @@ -0,0 +1,21 @@ +.node { + display: flex; + flex-direction: column; + gap: 24px; + font-size: var(--bit-p-m); + word-break: normal; + color: var(--bit-text-color); + padding-bottom: 10px; +} + +.typeTitle { + font-size: var(--bit-p-sm); + color: var(--bit-accent-primary-color); + margin-bottom: 16px; + margin-top: 10px; +} + +.typeBody { + padding: 8px; + border-radius: 5px; +} diff --git a/components/renderers/type-intersection/type-intersection.renderer.tsx b/components/renderers/type-intersection/type-intersection.renderer.tsx new file mode 100644 index 0000000..bb2e29c --- /dev/null +++ b/components/renderers/type-intersection/type-intersection.renderer.tsx @@ -0,0 +1,63 @@ +import React from 'react'; +import type { APINodeRenderProps, APINodeRenderer } from '@teambit/api-reference.models.api-node-renderer'; +import { nodeStyles } from '@teambit/api-reference.models.api-node-renderer'; +import { TypeIntersectionSchema } from '@teambit/semantics.entities.semantic-schema'; +import classnames from 'classnames'; + +import styles from './type-intersection.renderer.module.scss'; + +export const typeIntersectionRenderer: APINodeRenderer = { + predicate: (node) => node.__schema === TypeIntersectionSchema.name, + Component: TypeIntersectionComponent, + nodeType: 'TypeIntersection', + default: true, +}; + +function TypeIntersectionComponent(props: APINodeRenderProps) { + const { + apiNode: { api }, + renderers, + metadata, + } = props; + + const typeNode = api as TypeIntersectionSchema; + + return ( +
+ {typeNode.types.map((type, index) => { + const typeRenderer = renderers.find((renderer) => renderer.predicate(type)); + + if (typeRenderer) { + return ( +
+ {/* {type.name &&
{type.name}
} */} + { +
+ +
+ } +
+ ); + } + + return ( +
+ {type.toString()} + {typeNode.types.length > 1 && index !== typeNode.types.length - 1 ? ( +
+ {'&'} +
+ ) : null} +
+ ); + })} +
+ ); +} diff --git a/components/renderers/type-literal/index.ts b/components/renderers/type-literal/index.ts new file mode 100644 index 0000000..6475af2 --- /dev/null +++ b/components/renderers/type-literal/index.ts @@ -0,0 +1 @@ +export { typeLiteralRenderer } from './type-literal.renderer'; diff --git a/components/renderers/type-literal/type-literal.renderer.module.scss b/components/renderers/type-literal/type-literal.renderer.module.scss new file mode 100644 index 0000000..2aa9986 --- /dev/null +++ b/components/renderers/type-literal/type-literal.renderer.module.scss @@ -0,0 +1,14 @@ +.container { + height: auto; +} + +.node { + display: flex; + font-size: var(--bit-p-xs); + word-break: normal; + position: relative; + line-height: 1.2em; + background-color: var(--surface01-color, #fffbff); + padding: 4px; + border-radius: 4px; +} diff --git a/components/renderers/type-literal/type-literal.renderer.tsx b/components/renderers/type-literal/type-literal.renderer.tsx new file mode 100644 index 0000000..a0bedfc --- /dev/null +++ b/components/renderers/type-literal/type-literal.renderer.tsx @@ -0,0 +1,62 @@ +import React from 'react'; +import type { APINodeRenderProps, APINodeRenderer } from '@teambit/api-reference.models.api-node-renderer'; +import { nodeStyles } from '@teambit/api-reference.models.api-node-renderer'; +import { copySchemaNode } from '@teambit/api-reference.utils.copy-schema-node'; +import { GroupedSchemaNodesSummary } from '@teambit/api-reference.renderers.grouped-schema-nodes-summary'; +import { TypeLiteralSchema } from '@teambit/semantics.entities.semantic-schema'; +import { SchemaNodesSummary } from '@teambit/api-reference.overview.renderers.grouped-schema-nodes-overview-summary'; + +export const typeLiteralRenderer: APINodeRenderer = { + predicate: (node) => node.__schema === TypeLiteralSchema.name, + Component: TypeLiteralComponent, + OverviewComponent: TypeLiteralOverviewComponent, + nodeType: 'TypeLiteral', + default: true, +}; + +function TypeLiteralComponent(props: APINodeRenderProps) { + const { + apiNode: { api }, + } = props; + const typeLiteralNode = api as TypeLiteralSchema; + + if (props.metadata?.[api.__schema]?.columnView) { + return
{api.toString()}
; + } + + const members = typeLiteralNode.getNodes().map((node) => { + if (node.signature) return node; + return copySchemaNode(node, { signature: node.toString() }); + }); + return ( + + ); +} + +function TypeLiteralOverviewComponent(props: APINodeRenderProps) { + const { + apiNode: { api, renderer }, + } = props; + const typeLiteralNode = api as TypeLiteralSchema; + const { members, doc } = typeLiteralNode; + + const icon = renderer.icon; + const description = + doc?.comment ?? doc?.tags?.filter((tag) => tag.comment).reduce((acc, tag) => acc.concat(`${tag.comment}\n`), ''); + return ( + + ); +} diff --git a/components/renderers/type-ref/index.ts b/components/renderers/type-ref/index.ts new file mode 100644 index 0000000..dcaa4b9 --- /dev/null +++ b/components/renderers/type-ref/index.ts @@ -0,0 +1 @@ +export { typeRefRenderer, TypeRefName } from './type-ref.renderer'; diff --git a/components/renderers/type-ref/type-ref.renderer.module.scss b/components/renderers/type-ref/type-ref.renderer.module.scss new file mode 100644 index 0000000..e9bf54e --- /dev/null +++ b/components/renderers/type-ref/type-ref.renderer.module.scss @@ -0,0 +1,47 @@ +.node, +.nodeLink { + display: flex; + font-size: var(--bit-p-xs); + word-break: normal; + position: relative; + line-height: 1.2em; +} + +.node { + color: var(--bit-text-color-light, rgb(79, 79, 79)); /* a more subtle color */ +} + +.nodeLink { + color: var(--bit-accent-text, #4646c6); + font-size: var(--bit-p-xs); + display: flex; + gap: 4px; + + &:hover { + color: var(--bit-accent-text-heavy, var(--bit-accent-heavy, #5d4aec)); + } +} +// .package { +// } + +// .internalType { +// } +.typeArgs { + display: flex; + width: fit-content; + align-items: center; + justify-content: center; + padding: 0px 2px; +} + +.typeArgNode { + padding: 1px; +} + +.locationIcon { + display: flex; + img { + margin-right: 2px; + height: 12px; + } +} diff --git a/components/renderers/type-ref/type-ref.renderer.tsx b/components/renderers/type-ref/type-ref.renderer.tsx new file mode 100644 index 0000000..931197c --- /dev/null +++ b/components/renderers/type-ref/type-ref.renderer.tsx @@ -0,0 +1,205 @@ +import React from 'react'; +import type { APINodeRenderProps, APINodeRenderer } from '@teambit/api-reference.models.api-node-renderer'; +import { nodeStyles } from '@teambit/api-reference.models.api-node-renderer'; +import { TypeRefSchema } from '@teambit/semantics.entities.semantic-schema'; +import { APINodeDetails } from '@teambit/api-reference.renderers.api-node-details'; +import { copySchemaNode } from '@teambit/api-reference.utils.copy-schema-node'; +import classnames from 'classnames'; +import { useUpdatedUrlFromQuery } from '@teambit/api-reference.hooks.use-api-ref-url'; +import type { ComponentID } from '@teambit/component-id'; +import { ComponentUrl } from '@teambit/component.modules.component-url'; +import { Link as BaseLink } from '@teambit/base-react.navigation.link'; + +import styles from './type-ref.renderer.module.scss'; + +// @todo - this will be fixed as part of the @teambit/base-react.navigation.link upgrade to latest +const Link = BaseLink as any; + +export const typeRefRenderer: APINodeRenderer = { + predicate: (node) => node.__schema === TypeRefSchema.name, + Component: TypeRefComponent, + nodeType: 'TypeRefs', + icon: { name: 'TypeRef', url: 'https://static.bit.dev/api-reference/type.svg' }, + default: true, +}; + +function TypeRefComponent(props: APINodeRenderProps) { + const { + apiNode: { api }, + apiRefModel, + depth, + renderers, + } = props; + const typeRefNode = api as TypeRefSchema; + const TypeRefWrapper = ({ children }: { children: React.ReactNode }) => + depth === 0 ? ( + + {children} + + ) : ( + <>{children} + ); + + const exportedTypeFromSameComp = typeRefNode.isFromThisComponent() + ? apiRefModel.getByName(typeRefNode, typeRefNode.internalFilePath) + : undefined; + + const exportedTypeUrlFromSameComp = + exportedTypeFromSameComp && + useUpdatedUrlFromQuery({ + selectedAPI: typeRefNode.isInternalReference() + ? apiRefModel.internalAPIKey(typeRefNode, typeRefNode.internalFilePath) + : exportedTypeFromSameComp.api.name, + }); + + const exportedTypeUrlFromAnotherComp = typeRefNode.componentId + ? getExportedTypeUrlFromAnotherComp({ + componentId: typeRefNode.componentId, + selectedAPI: typeRefNode.name, + }) + : undefined; + + const packageUrl = typeRefNode.packageName ? `https://www.npmjs.com/package/${typeRefNode.packageName}` : undefined; + + const args = + typeRefNode.typeArgs?.map((typeArg, index, typeArgs) => { + const typeArgRenderer = renderers.find((renderer) => renderer.predicate(typeArg)); + + if (typeArgRenderer) { + return ( + + + {(typeArgs?.length ?? 0) > 1 && index !== (typeArgs?.length ?? 0) - 1 ? ', ' : null} + + ); + } + + return ( + + {typeArg.toString()} + {(typeArgs?.length ?? 0) > 1 && index !== (typeArgs?.length ?? 0) - 1 ? ', ' : null} + + ); + }) ?? null; + + if (args) { + return ( + + + +
+ {'<'} + {args.map((arg) => arg)} + {'>'} +
+
+
+
+ ); + } + + return ( + + + + ); +} + +const LinkContext = React.createContext(false); + +export function TypeRefName({ + name, + url, + external, + children, + internal, + exported, + packageName, +}: { + name: string; + url?: string; + external?: boolean; + children?: React.ReactNode; + internal?: boolean; + exported?: boolean; + packageName?: string; +}) { + const className = classnames(nodeStyles.node, { + [styles.internalType]: internal, + [styles.exportedType]: exported, + [styles.package]: !!packageName, + }); + + // Check if current component is nested within a Link. + const withinLink = React.useContext(LinkContext); + + if (url && !withinLink) { + return ( + + + {name} + {external && ( +
+ +
+ )} + {children} + +
+ ); + } + + return ( +
+ {name} + {children} +
+ ); +} + +function getExportedTypeUrlFromAnotherComp({ + componentId, + selectedAPI, +}: { + componentId: ComponentID; + selectedAPI: string; +}) { + const componentUrl = ComponentUrl.toUrl(componentId, { useLocationOrigin: false, includeVersion: true }); + const [componentIdUrl, versionQuery] = componentUrl.split('?'); + + const exportedTypeUrl = `${componentIdUrl}/~api-reference?selectedAPI=${encodeURIComponent(selectedAPI)}${ + versionQuery ? `&${versionQuery}` : '' + }`; + + return exportedTypeUrl; +} diff --git a/components/renderers/type-union/index.ts b/components/renderers/type-union/index.ts new file mode 100644 index 0000000..c281662 --- /dev/null +++ b/components/renderers/type-union/index.ts @@ -0,0 +1 @@ +export { typeUnionRenderer } from './type-union.renderer'; diff --git a/components/renderers/type-union/type-union.renderer.module.scss b/components/renderers/type-union/type-union.renderer.module.scss new file mode 100644 index 0000000..0342489 --- /dev/null +++ b/components/renderers/type-union/type-union.renderer.module.scss @@ -0,0 +1,11 @@ +.node { + display: flex; + font-family: Roboto mono; + font-size: var(--bit-p-xxs); + word-break: normal; + color: rgb(57, 115, 0); +} + +.separator { + padding: 0px 4px; +} diff --git a/components/renderers/type-union/type-union.renderer.tsx b/components/renderers/type-union/type-union.renderer.tsx new file mode 100644 index 0000000..550a2d1 --- /dev/null +++ b/components/renderers/type-union/type-union.renderer.tsx @@ -0,0 +1,60 @@ +import React from 'react'; +import type { APINodeRenderProps, APINodeRenderer } from '@teambit/api-reference.models.api-node-renderer'; +import { nodeStyles } from '@teambit/api-reference.models.api-node-renderer'; +import { TypeUnionSchema } from '@teambit/semantics.entities.semantic-schema'; + +import classnames from 'classnames'; +import styles from './type-union.renderer.module.scss'; + +export const typeUnionRenderer: APINodeRenderer = { + predicate: (node) => node.__schema === TypeUnionSchema.name, + Component: TypeUnionComponent, + nodeType: 'TypeUnion', + default: true, +}; + +function TypeUnionComponent(props: APINodeRenderProps) { + const { + apiNode: { api }, + renderers, + } = props; + const typeNode = api as TypeUnionSchema; + + return ( +
+ {typeNode.types.map((type, index, types) => { + const typeRenderer = renderers.find((renderer) => renderer.predicate(type)); + + if (typeRenderer) { + return ( + + + {types.length > 1 && index !== types.length - 1 ? ( +
+ {'|'} +
+ ) : null} +
+ ); + } + + return ( +
+ {type.toString()} + {types.length > 1 && index !== types.length - 1 ? ( +
+ {'|'} +
+ ) : null} +
+ ); + })} +
+ ); +} diff --git a/components/renderers/type/index.ts b/components/renderers/type/index.ts new file mode 100644 index 0000000..4934059 --- /dev/null +++ b/components/renderers/type/index.ts @@ -0,0 +1 @@ +export { typeRenderer } from './type.renderer'; diff --git a/components/renderers/type/type.renderer.module.scss b/components/renderers/type/type.renderer.module.scss new file mode 100644 index 0000000..e69de29 diff --git a/components/renderers/type/type.renderer.tsx b/components/renderers/type/type.renderer.tsx new file mode 100644 index 0000000..ab77562 --- /dev/null +++ b/components/renderers/type/type.renderer.tsx @@ -0,0 +1,63 @@ +import React from 'react'; +import { TypeSchema } from '@teambit/semantics.entities.semantic-schema'; +import type { APINodeRenderProps, APINodeRenderer } from '@teambit/api-reference.models.api-node-renderer'; +import { SchemaNodesSummary } from '@teambit/api-reference.overview.renderers.grouped-schema-nodes-overview-summary'; +import { APINodeDetails } from '@teambit/api-reference.renderers.api-node-details'; + +export const typeRenderer: APINodeRenderer = { + predicate: (node) => node.__schema === TypeSchema.name, + Component: TypeComponent, + nodeType: 'Types', + icon: { name: 'Type', url: 'https://static.bit.dev/api-reference/type.svg' }, + default: true, + OverviewComponent: TypeOverviewComponent, +}; + +function TypeComponent(props: APINodeRenderProps) { + const { + apiNode: { api }, + renderers, + metadata, + } = props; + const typeNode = api as TypeSchema; + const { type } = typeNode; + + const subTypeRenderer = renderers.find((renderer) => renderer.predicate(type)); + + if (metadata?.[api.__schema]?.columnView) { + return ( + (subTypeRenderer && ( + + )) || + null + ); + } + + return ( + + {subTypeRenderer && ( + + )} + + ); +} + +function TypeOverviewComponent(props: APINodeRenderProps) { + const { + apiNode: { api, renderer }, + } = props; + const typeNode = api as TypeSchema; + const icon = renderer?.icon; + const { type } = typeNode; + const nodes = type.getNodes(); + return ; +} diff --git a/components/renderers/unresolved/index.ts b/components/renderers/unresolved/index.ts new file mode 100644 index 0000000..99aa237 --- /dev/null +++ b/components/renderers/unresolved/index.ts @@ -0,0 +1 @@ +export { unresolvedRenderer } from './unresolved.renderer'; diff --git a/components/renderers/unresolved/unresolved.renderer.module.scss b/components/renderers/unresolved/unresolved.renderer.module.scss new file mode 100644 index 0000000..e69de29 diff --git a/components/renderers/unresolved/unresolved.renderer.tsx b/components/renderers/unresolved/unresolved.renderer.tsx new file mode 100644 index 0000000..1dbe026 --- /dev/null +++ b/components/renderers/unresolved/unresolved.renderer.tsx @@ -0,0 +1,16 @@ +import React from 'react'; +import type { APINodeRenderer, APINodeRenderProps } from '@teambit/api-reference.models.api-node-renderer'; +import { UnresolvedSchema } from '@teambit/semantics.entities.semantic-schema'; +import { APINodeDetails } from '@teambit/api-reference.renderers.api-node-details'; + +export const unresolvedRenderer: APINodeRenderer = { + predicate: (node) => node.__schema === UnresolvedSchema.name, + Component: UnresolvedComponent, + nodeType: 'Unresolved', + icon: { name: 'Unresolved', url: '' }, + default: true, +}; + +function UnresolvedComponent(props: APINodeRenderProps) { + return ; +} diff --git a/components/renderers/variable/index.ts b/components/renderers/variable/index.ts new file mode 100644 index 0000000..49f4a6f --- /dev/null +++ b/components/renderers/variable/index.ts @@ -0,0 +1 @@ +export { variableRenderer } from './variable.renderer'; diff --git a/components/renderers/variable/variable.renderer.module.scss b/components/renderers/variable/variable.renderer.module.scss new file mode 100644 index 0000000..e69de29 diff --git a/components/renderers/variable/variable.renderer.tsx b/components/renderers/variable/variable.renderer.tsx new file mode 100644 index 0000000..0cc3db3 --- /dev/null +++ b/components/renderers/variable/variable.renderer.tsx @@ -0,0 +1,32 @@ +import React from 'react'; +import { VariableLikeSchema } from '@teambit/semantics.entities.semantic-schema'; +import type { APINodeRenderProps, APINodeRenderer } from '@teambit/api-reference.models.api-node-renderer'; +import { nodeStyles } from '@teambit/api-reference.models.api-node-renderer'; +import { APINodeDetails } from '@teambit/api-reference.renderers.api-node-details'; + +export const variableRenderer: APINodeRenderer = { + predicate: (node) => node.__schema === VariableLikeSchema.name, + Component: VariableComponent, + nodeType: 'Variables', + icon: { name: 'Variable', url: 'https://static.bit.dev/api-reference/variable.svg' }, + default: true, +}; + +function VariableComponent(props: APINodeRenderProps) { + const api = props.apiNode.api as VariableLikeSchema; + if (props.depth) { + const type = api.type; + const typeRenderer = props.renderers.find((renderer) => renderer.predicate(type)); + return typeRenderer ? ( + + ) : ( +
{type.toString()}
+ ); + } + return ; +} diff --git a/components/sections/api-reference-page/api-reference-page.module.scss b/components/sections/api-reference-page/api-reference-page.module.scss new file mode 100644 index 0000000..ebf3150 --- /dev/null +++ b/components/sections/api-reference-page/api-reference-page.module.scss @@ -0,0 +1,105 @@ +@import '@teambit/ui-foundation.ui.constants.z-indexes/z-indexes.module.scss'; + +.apiRefPageContainer { + height: 100%; + width: 100%; + display: flex; + flex-direction: column; +} +.selectedAPIDetailsContainer { + padding: 24px 40px; + display: flex; + flex-direction: column; + height: calc(100% - 80px); + width: calc(100% - 80px); +} +.splitter { + position: relative; + + > :first-child { + z-index: $pane-splitter-zIndex; + position: absolute; + } +} +.collapser { + right: 1px; +} +.right { + position: relative; + background: var(--background-color); + overflow-y: auto; + // this is to fix the right panel when it gets too big or too small + min-width: 200px; + max-width: calc(100% - 200px); +} +.left { + min-width: 200px; + //cannot assign max-width directly to right side + max-width: calc(100% - 200px); + display: flex; +} + +.loader { + display: flex; + align-items: center; + justify-items: center; + height: 100%; + width: 100%; +} + +.apiNodeDetailsNameContainer { + display: flex; + flex-direction: row; +} + +.apiTypeIcon { + img { + height: var(--bit-h-md, 26px); + } + padding-right: 8px; +} + +.name { + margin: 0; +} + +.selectedAPILocationContainer { + display: flex; + align-items: center; + margin-left: 8px; + padding-right: 8px; + border: 1px solid var(--bit-border-color-lightest, #ededed); + border-radius: 6px; + &:hover { + background-color: var(--bit-bg-heavy, #f4f5f6); + } +} + +.locationLabel { + display: flex; + color: var(--bit-text-color-light, '#707279'); + padding: 4px; +} +.locationIcon { + display: flex; + img { + height: 12px; + } +} + +.locationLink { + display: flex; + align-items: center; + align-self: center; + min-width: fit-content; + padding-right: 4px; + margin-left: 12px; + font-size: var(--bit-p-xxs, '12px'); + color: var(--bit-text-color-light, '#707279'); + text-decoration: none; + border: 1px solid var(--bit-border-color-lightest, #ededed); + border-radius: 6px; + &:hover { + background-color: var(--bit-bg-heavy, #f4f5f6); + } +} diff --git a/components/sections/api-reference-page/api-reference-page.tsx b/components/sections/api-reference-page/api-reference-page.tsx new file mode 100644 index 0000000..a8cc68d --- /dev/null +++ b/components/sections/api-reference-page/api-reference-page.tsx @@ -0,0 +1,171 @@ +import type { HTMLAttributes } from 'react'; +import React, { useContext, useState, useMemo } from 'react'; +import flatten from 'lodash.flatten'; +import classNames from 'classnames'; +import { ComponentContext } from '@teambit/component'; +import { H1 } from '@teambit/documenter.ui.heading'; +import { useIsMobile } from '@teambit/ui-foundation.ui.hooks.use-is-mobile'; +import { Link as BaseLink } from '@teambit/base-react.navigation.link'; +import { useQuery } from '@teambit/ui-foundation.ui.react-router.use-query'; +import { Collapser } from '@teambit/ui-foundation.ui.buttons.collapser'; +import { HoverSplitter } from '@teambit/base-ui.surfaces.split-pane.hover-splitter'; +import { SplitPane, Pane, Layout } from '@teambit/base-ui.surfaces.split-pane.split-pane'; +import { useAPI } from '@teambit/api-reference.hooks.use-api'; +import { APIReferenceExplorer } from '@teambit/api-reference.explorer.api-reference-explorer'; +import { useAPIRefParam } from '@teambit/api-reference.hooks.use-api-ref-url'; +import type { APINodeRendererSlot } from '@teambit/api-reference'; +import { sortAPINodes } from '@teambit/api-reference.utils.sort-api-nodes'; +import type { TreeNode } from '@teambit/design.ui.tree'; +import { RoundLoader } from '@teambit/design.ui.round-loader'; +import { EmptyBox } from '@teambit/design.ui.empty-box'; +import { ComponentUrl } from '@teambit/component.modules.component-url'; +import { useLanes } from '@teambit/lanes.hooks.use-lanes'; +import { LanesModel } from '@teambit/lanes.ui.models.lanes-model'; + +import styles from './api-reference-page.module.scss'; + +// @todo - this will be fixed as part of the @teambit/base-react.navigation.link upgrade to latest +const Link = BaseLink as any; + +export type APIRefPageProps = { + host: string; + rendererSlot: APINodeRendererSlot; +} & HTMLAttributes; + +export function APIRefPage({ rendererSlot, className }: APIRefPageProps) { + const component = useContext(ComponentContext); + const lanes = useLanes(); + const renderers = flatten(rendererSlot.values()); + const { apiModel, loading } = useAPI(component.id.toString(), renderers); + const isMobile = useIsMobile(); + const [isSidebarOpen, setSidebarOpenness] = useState(!isMobile); + const [mounted, setMounted] = useState(false); + + React.useEffect(() => { + setMounted(true); + }, []); + + const sidebarOpenness = isSidebarOpen ? Layout.row : Layout.left; + + const selectedAPIFromUrl = useAPIRefParam('selectedAPI'); + + React.useEffect(() => { + window.scrollTo(0, 0); + }, [selectedAPIFromUrl]); + + const apiNodes = (apiModel && flatten(Array.from(apiModel.apiByType.values())).sort(sortAPINodes)) || []; + + const isEmpty = apiNodes.length === 0; + + const apiTree: string[] = useMemo(() => { + return apiNodes.map((apiNode) => { + if (!apiNode.exported) return `_Internals/${apiModel?.internalAPIKey(apiNode.api)}`; + return `${apiNode.renderer?.nodeType}/${apiNode.alias || apiNode.api.name}`; + }); + }, [apiNodes.length]); + + const getIcon = (node: TreeNode) => { + const nodeType = node.id.split('/')[0]; + if (nodeType === '_Internals') { + const apiNode = apiModel?.apiByName.get(node.id.replace('_Internals/', '')); + const icon = apiNode?.renderer.icon?.url; + return icon || undefined; + } + const icon = apiModel?.apiByType.get(nodeType)?.[0].renderer.icon?.url; + return icon || undefined; + }; + + const selectedAPINode = + (selectedAPIFromUrl && + (apiModel?.apiByName.get(selectedAPIFromUrl) || + apiModel?.apiByName.get(selectedAPIFromUrl.replace('_Internals/', '')))) || + apiNodes[0]; + + const selectedAPIName = + (selectedAPINode && selectedAPINode.exported + ? `${selectedAPINode?.renderer?.nodeType}/${selectedAPINode?.alias || selectedAPINode?.api.name}` + : selectedAPINode && `_Internals/${apiModel?.internalAPIKey(selectedAPINode.api)}`) || apiTree[0]; + + const SelectedAPIComponent = selectedAPINode && selectedAPINode.renderer.Component; + const query = useQuery(); + + if (loading) { + return ( +
+ +
+ ); + } + + if (!apiModel || isEmpty) { + return ; + } + const icon = selectedAPINode.renderer.icon; + const name = selectedAPINode.api.name; + const componentVersionFromUrl = query.get('version'); + const filePath = selectedAPINode.api.location.filePath; + + const pathname = ComponentUrl.toUrl(component.id, { includeVersion: false, useLocationOrigin: true }); + const componentUrlWithoutVersion = pathname?.split('~')[0]; + + const viewedLaneId = lanes.lanesModel?.viewedLane?.id; + const laneComponentUrl = + viewedLaneId && !viewedLaneId.isDefault() && mounted + ? `${window.location.origin}${LanesModel.getLaneComponentUrl(component.id, viewedLaneId)}/~code/${filePath}${ + componentVersionFromUrl ? `?version=${componentVersionFromUrl}` : '' + }` + : undefined; + + const mainComponentUrl = `${componentUrlWithoutVersion}/~code/${filePath}${ + componentVersionFromUrl ? `?version=${componentVersionFromUrl}` : '' + }`; + + const locationUrl = laneComponentUrl || mainComponentUrl; + + return ( + + +
+
+ {icon && ( +
+ +
+ )} +

+ {name} +

+ +
+ {SelectedAPIComponent && ( + + )} +
+
+ + e.stopPropagation()} // avoid split-pane drag + onClick={() => setSidebarOpenness((x) => !x)} + tooltipContent={`${isSidebarOpen ? 'Hide' : 'Show'} file tree`} + className={styles.collapser} + /> + + + + +
+ ); +} + +function SelectedAPILocation({ locationUrl }: { locationUrl: string }) { + return ( + +
View Code
+
+ +
+ + ); +} diff --git a/components/sections/api-reference-page/index.ts b/components/sections/api-reference-page/index.ts new file mode 100644 index 0000000..afac225 --- /dev/null +++ b/components/sections/api-reference-page/index.ts @@ -0,0 +1 @@ +export { APIRefPage, APIRefPageProps } from './api-reference-page'; diff --git a/components/sections/api-reference-section/api-reference.section.tsx b/components/sections/api-reference-section/api-reference.section.tsx new file mode 100644 index 0000000..afc076e --- /dev/null +++ b/components/sections/api-reference-section/api-reference.section.tsx @@ -0,0 +1,18 @@ +import type { Section } from '@teambit/component'; +import type { APIReferenceUI } from '@teambit/api-reference'; + +export class APIRefSection implements Section { + constructor(private apiReferenceUI: APIReferenceUI) {} + + order = 100; + + route = { + path: '~api-reference', + element: this.apiReferenceUI.getAPIPage(), + }; + + navigationLink = { + href: '~api-reference', + children: 'API Reference', + }; +} diff --git a/components/sections/api-reference-section/index.ts b/components/sections/api-reference-section/index.ts new file mode 100644 index 0000000..d817c81 --- /dev/null +++ b/components/sections/api-reference-section/index.ts @@ -0,0 +1 @@ +export { APIRefSection } from './api-reference.section'; diff --git a/components/tagged-exports/index.ts b/components/tagged-exports/index.ts new file mode 100644 index 0000000..0970a09 --- /dev/null +++ b/components/tagged-exports/index.ts @@ -0,0 +1,2 @@ +export { TaggedExports } from './tagged-exports'; +export type { TaggedExportsProps } from './tagged-exports'; diff --git a/components/tagged-exports/tagged-exports.module.scss b/components/tagged-exports/tagged-exports.module.scss new file mode 100644 index 0000000..8564181 --- /dev/null +++ b/components/tagged-exports/tagged-exports.module.scss @@ -0,0 +1,72 @@ +.section { + padding: 20px 40px 10px 40px; + display: flex; + flex-direction: column; + gap: 32px; +} + +.title { + display: flex; + align-items: center; + justify-content: center; + gap: 8px; +} + +.heading { + margin-bottom: 0; +} + +.content { + display: flex; + border-radius: 8px; +} + +// Override hardcoded light background in @teambit/api-reference.renderers.react +.section :global([class*='reactOverview']) { + background-color: var(--surface01-color, rgba(247, 247, 245, 0.63)); +} + +.banner { + display: flex; + align-items: center; + width: fit-content; + background-color: var(--surface01-color, #f0f0f0); + border-radius: 8px; + padding: 16px 32px; + font-size: 16px; + gap: 8px; + > img { + background-color: none; + } +} + +.highlighted { + color: var(--primary-color, #6c5ce7); + margin: 0px 4px; +} + +.loader { + display: flex; + flex-direction: column; + gap: 16px; + color: var(--bit-accent-bg, #f0f0f0); +} + +.loaderTitle { + display: flex; + gap: 8px; +} + +.apiSkeleton { + margin-top: 8px; + border-radius: 4px; + align-items: center; +} + +.bannerSkeleton { + display: flex; + align-items: center; + gap: 8px; + padding: 8px; + border-radius: 8px; +} diff --git a/components/tagged-exports/tagged-exports.tsx b/components/tagged-exports/tagged-exports.tsx new file mode 100644 index 0000000..5f36f35 --- /dev/null +++ b/components/tagged-exports/tagged-exports.tsx @@ -0,0 +1,98 @@ +import React from 'react'; +import { LinkedHeading } from '@teambit/documenter.ui.linked-heading'; +import { Section } from '@teambit/documenter.ui.section'; +import { useAPI } from '@teambit/api-reference.hooks.use-api'; +import { BlockSkeleton, WordSkeleton, CircleSkeleton } from '@teambit/base-ui.loaders.skeleton'; +import { useAPIRefRenderers } from '@teambit/api-reference.hooks.use-api-renderers'; +import { APIReferenceTableOfContents } from '@teambit/api-reference.overview.api-reference-table-of-contents'; +import type { APIReferenceModel } from '@teambit/api-reference.models.api-reference-model'; +import { useThemePicker } from '@teambit/base-react.themes.theme-switcher'; + +import styles from './tagged-exports.module.scss'; + +export type TaggedExportsProps = { + componentId: string; + showBanner?: boolean; +} & React.HTMLAttributes; + +export function TaggedExports({ componentId, showBanner, ...rest }: TaggedExportsProps) { + const renderers = useAPIRefRenderers(); + const api = useAPI(componentId, renderers.nodeRenderers); + const showTableOfContents = api.apiModel?.taggedAPINodes.length === 0; + const theme = useThemePicker(); + const taggedAPIs = api.apiModel?.taggedAPINodes; + const loading = !!api.loading; + + if (loading) { + return ( +
+
+
+ + +
+ + + +
+
+ ); + } + + if (!api.apiModel || !api.apiModel.apiNodes.length) return null; + + return ( +
+ +
+ + API +
+
+ {showTableOfContents && ( +
+ +
+ )} + { +
+ {taggedAPIs?.map((taggedAPI, index) => { + const OverviewComponent = taggedAPI.renderer.OverviewComponent; + // @todo - change this to be non nullable + if (!OverviewComponent) return null; + return ( + + ); + })} +
+ } + {showBanner && ( +
+ + + Use the + @exports + jsdoc tag to only show relevant APIs with details for your users + +
+ )} +
+ ); +} diff --git a/components/utils/code-editor-options/code-editor-options.ts b/components/utils/code-editor-options/code-editor-options.ts new file mode 100644 index 0000000..bc3a36c --- /dev/null +++ b/components/utils/code-editor-options/code-editor-options.ts @@ -0,0 +1,26 @@ +import type * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; + +export const defaultCodeEditorOptions: monaco.editor.IStandaloneEditorConstructionOptions = { + minimap: { enabled: false }, + scrollbar: { vertical: 'hidden', alwaysConsumeMouseWheel: false }, + scrollBeyondLastLine: false, + readOnly: true, + language: 'typescript', + lineNumbers: 'off', + folding: false, + overviewRulerLanes: 0, + overviewRulerBorder: false, + wordWrap: 'on', + automaticLayout: true, + wrappingStrategy: 'advanced', + fixedOverflowWidgets: true, + hover: { + delay: 150, + }, + parameterHints: { + enabled: false, + }, + renderLineHighlight: 'none', + lineHeight: 20, + padding: { top: 8, bottom: 8 }, +}; diff --git a/components/utils/code-editor-options/index.ts b/components/utils/code-editor-options/index.ts new file mode 100644 index 0000000..ca88dd2 --- /dev/null +++ b/components/utils/code-editor-options/index.ts @@ -0,0 +1 @@ +export { defaultCodeEditorOptions } from './code-editor-options'; diff --git a/components/utils/copy-schema-node/copy-schema-node.ts b/components/utils/copy-schema-node/copy-schema-node.ts new file mode 100644 index 0000000..364a2f2 --- /dev/null +++ b/components/utils/copy-schema-node/copy-schema-node.ts @@ -0,0 +1,15 @@ +import type { SchemaNode, ISchemaNode } from '@teambit/semantics.entities.semantic-schema'; + +export function copySchemaNode(source: ISchemaNode, update: Partial): SchemaNode { + return { + ...source, + ...update, + location: update.location || source.location, + toString: update.toString || source.toString, + toObject: update.toObject || source.toObject, + getNodes: update.getNodes || source.getNodes, + findNode: update.findNode || source.findNode, + getAllNodesRecursively: update.getAllNodesRecursively || source.getAllNodesRecursively, + displaySchemaName: update.displaySchemaName || source.displaySchemaName, + }; +} diff --git a/components/utils/copy-schema-node/index.ts b/components/utils/copy-schema-node/index.ts new file mode 100644 index 0000000..d4e3a5c --- /dev/null +++ b/components/utils/copy-schema-node/index.ts @@ -0,0 +1 @@ +export { copySchemaNode } from './copy-schema-node'; diff --git a/components/utils/custom-prism-syntax-highlighter-theme/custom-theme.js b/components/utils/custom-prism-syntax-highlighter-theme/custom-theme.js new file mode 100644 index 0000000..351b548 --- /dev/null +++ b/components/utils/custom-prism-syntax-highlighter-theme/custom-theme.js @@ -0,0 +1,198 @@ +export default { + 'code[class*="language-"]': { + fontFamily: '"Fira Code", "Fira Mono", Menlo, Consolas, "DejaVu Sans Mono", monospace', + fontSize: '14px', + lineHeight: '1.5', + direction: 'ltr', + textAlign: 'left', + whiteSpace: 'pre', + wordSpacing: 'normal', + wordBreak: 'normal', + MozTabSize: '2', + OTabSize: '2', + tabSize: '2', + WebkitHyphens: 'none', + MozHyphens: 'none', + msHyphens: 'none', + hyphens: 'none', + background: '#F1F2F4', + color: '#6c5ce7', + }, + 'pre[class*="language-"]': { + fontFamily: '"Fira Code", "Fira Mono", Menlo, Consolas, "DejaVu Sans Mono", monospace', + fontSize: '14px', + lineHeight: '1.5', + direction: 'ltr', + textAlign: 'left', + whiteSpace: 'pre', + wordSpacing: 'normal', + wordBreak: 'normal', + MozTabSize: '2', + OTabSize: '2', + tabSize: '2', + WebkitHyphens: 'none', + MozHyphens: 'none', + msHyphens: 'none', + hyphens: 'none', + background: '#F1F2F4', + color: '#6c5ce7', + padding: '1em', + margin: '.5em 0', + borderRadius: '.3em', + overflow: 'auto', + }, + 'pre > code[class*="language-"]': { + fontSize: '1em', + }, + 'code[class*="language-"] ::-moz-selection': { + textShadow: 'none', + background: 'hsl(230, 1%, 90%)', + }, + 'pre[class*="language-"]::selection': { + textShadow: 'none', + background: 'hsl(230, 1%, 90%)', + }, + ':not(pre) > code[class*="language-"]': { + padding: '0.2em 0.3em', + borderRadius: '.3em', + }, + comment: { + color: 'hsl(230, 4%, 64%)', + }, + prolog: { + color: 'hsl(230, 4%, 64%)', + }, + doctype: { + color: 'hsl(230, 8%, 24%)', + }, + cdata: { + color: 'hsl(230, 4%, 64%)', + }, + punctuation: { + color: 'hsl(230, 8%, 24%)', + }, + namespace: { + Opacity: '.7', + }, + tag: { + color: 'hsl(5, 74%, 59%)', + }, + operator: { + color: 'hsl(221, 87%, 60%)', + }, + number: { + color: '#b800b8', + }, + property: { + color: '#111b27', + }, + function: { + color: '#b800b8', + }, + 'tag-id': { + color: '#2d2006', + }, + selector: { + color: '#2d2006', + }, + 'atrule-id': { + color: '#2d2006', + }, + 'code.language-javascript': { + color: '#896724', + }, + 'attr-name': { + color: '#896724', + }, + 'code.language-css': { + color: 'hsl(230, 8%, 24%)', + }, + 'code.language-scss': { + color: 'hsl(230, 8%, 24%)', + }, + boolean: { + color: '#b800b8', + }, + string: { + color: 'hsl(119, 34%, 47%)', + }, + entity: { + color: 'hsl(230, 8%, 24%)', + cursor: 'help', + }, + url: { + color: 'hsl(230, 8%, 24%)', + }, + '.language-css .token.string': { + color: 'hsl(230, 8%, 24%)', + }, + '.language-scss .token.string': { + color: 'hsl(230, 8%, 24%)', + }, + '.style .token.string': { + color: 'hsl(230, 8%, 24%)', + }, + 'attr-value': { + color: 'hsl(230, 8%, 24%)', + }, + builtin: { + color: 'hsla(301, 63%, 40%, 1)', + }, + keyword: { + color: 'hsla(301, 63%, 40%, 1)', + }, + control: { + color: 'hsl(230, 8%, 24%)', + }, + directive: { + color: 'hsl(230, 8%, 24%)', + }, + unit: { + color: 'hsl(230, 8%, 24%)', + }, + statement: { + color: 'hsl(230, 8%, 24%)', + }, + regex: { + color: 'hsl(230, 8%, 24%)', + }, + atrule: { + color: '#b800b8', + }, + placeholder: { + color: '#93abdc', + }, + variable: { + color: 'hsl(221, 87%, 60%);', + }, + deleted: { + textDecoration: 'line-through', + }, + inserted: { + borderBottom: '1px dotted #2d2006', + textDecoration: 'none', + }, + italic: { + fontStyle: 'italic', + }, + important: { + fontWeight: 'bold', + color: '#896724', + }, + bold: { + fontWeight: 'bold', + }, + 'pre > code.highlight': { + Outline: '.4em solid #896724', + OutlineOffset: '.4em', + }, + '.line-numbers .line-numbers-rows': { + borderRightColor: '#ece8de', + }, + '.line-numbers-rows > span:before': { + color: '#cdc4b1', + }, + '.line-highlight': { + background: 'linear-gradient(to right, rgba(45, 32, 6, 0.2) 70%, rgba(45, 32, 6, 0))', + }, +}; diff --git a/components/utils/custom-prism-syntax-highlighter-theme/index.ts b/components/utils/custom-prism-syntax-highlighter-theme/index.ts new file mode 100644 index 0000000..6d7646a --- /dev/null +++ b/components/utils/custom-prism-syntax-highlighter-theme/index.ts @@ -0,0 +1,4 @@ +import _DefaultBitPrismTheme from './custom-theme'; + +const DefaultBitPrismTheme = _DefaultBitPrismTheme as { [key: string]: React.CSSProperties }; +export default DefaultBitPrismTheme; diff --git a/components/utils/group-schema-node-by-signature/group-schema-node-by-signature.ts b/components/utils/group-schema-node-by-signature/group-schema-node-by-signature.ts new file mode 100644 index 0000000..7fa2f48 --- /dev/null +++ b/components/utils/group-schema-node-by-signature/group-schema-node-by-signature.ts @@ -0,0 +1,47 @@ +import type { SchemaNode } from '@teambit/semantics.entities.semantic-schema'; +import { ConstructorSchema } from '@teambit/semantics.entities.semantic-schema'; +import pluralize from 'pluralize'; + +export function sortSignatureType( + [aType]: [string | undefined, SchemaNode[]], + [bType]: [string | undefined, SchemaNode[]] +): 0 | 1 | -1 { + if (!aType) return -1; + if (!bType) return 1; + if (aType === 'constructors') return -1; + if (bType === 'constructors') return 1; + if (aType === 'properties') return -1; + if (bType === 'properties') return 1; + if (aType < bType) return -1; + if (aType > bType) return 1; + return 0; +} + +export function groupByNodeSignatureType(nodes: SchemaNode[]): Map { + return nodes.reduce((acc, next) => { + const { signature, __schema } = next; + if (!signature) return acc; + let type: string | undefined; + + if (__schema === ConstructorSchema.name) { + type = pluralize('constructor'); + } + + if (!type && signature.startsWith('(') && signature.includes('):')) { + type = pluralize('method'); + } + + if (!type && signature.startsWith('[') && signature.includes(']:')) { + type = pluralize('property'); + } + + if (!type) { + const extractedType = signature.split(') ')[0].split('(')[1]; + type = extractedType ? pluralize(extractedType) : undefined; + } + + const existing = acc.get(type) || []; + acc.set(type, existing.concat(next)); + return acc; + }, new Map()); +} diff --git a/components/utils/group-schema-node-by-signature/index.ts b/components/utils/group-schema-node-by-signature/index.ts new file mode 100644 index 0000000..4e305f5 --- /dev/null +++ b/components/utils/group-schema-node-by-signature/index.ts @@ -0,0 +1 @@ +export { groupByNodeSignatureType, sortSignatureType } from './group-schema-node-by-signature'; diff --git a/components/utils/schema-node-signature-transform/index.ts b/components/utils/schema-node-signature-transform/index.ts new file mode 100644 index 0000000..86dd314 --- /dev/null +++ b/components/utils/schema-node-signature-transform/index.ts @@ -0,0 +1 @@ +export { transformSignature } from './schema-node-signature.transform'; diff --git a/components/utils/schema-node-signature-transform/schema-node-signature.transform.ts b/components/utils/schema-node-signature-transform/schema-node-signature.transform.ts new file mode 100644 index 0000000..a6a09b7 --- /dev/null +++ b/components/utils/schema-node-signature-transform/schema-node-signature.transform.ts @@ -0,0 +1,12 @@ +import type { SchemaNode } from '@teambit/semantics.entities.semantic-schema'; +import { ConstructorSchema } from '@teambit/semantics.entities.semantic-schema'; + +export function transformSignature({ __schema, signature }: SchemaNode): string | undefined { + if (!signature) return undefined; + if (__schema === ConstructorSchema.name && 'constructor') return signature; + const displaySignatureIndex = signature.indexOf(') ') + 1; + const [, ...displaySignature] = signature.includes('.') + ? signature.slice(displaySignatureIndex).trim().split('.') + : [undefined, signature.slice(displaySignatureIndex).trim()]; + return displaySignature.join('.'); +} diff --git a/components/utils/sort-api-nodes/index.ts b/components/utils/sort-api-nodes/index.ts new file mode 100644 index 0000000..7d3f5b6 --- /dev/null +++ b/components/utils/sort-api-nodes/index.ts @@ -0,0 +1 @@ +export { sortAPINodes } from './sort-api-nodes'; diff --git a/components/utils/sort-api-nodes/sort-api-nodes.ts b/components/utils/sort-api-nodes/sort-api-nodes.ts new file mode 100644 index 0000000..b28cb69 --- /dev/null +++ b/components/utils/sort-api-nodes/sort-api-nodes.ts @@ -0,0 +1,19 @@ +import type { APINode } from '@teambit/api-reference.models.api-reference-model'; + +export function sortAPINodes(apiNodeA: APINode, apiNodeB: APINode): 1 | -1 | 0 { + const aNodeType = apiNodeA.renderer.nodeType; + const bNodeType = apiNodeB.renderer.nodeType; + + if (!apiNodeA.exported) return 1; + if (!apiNodeB.exported) return 1; + + if (aNodeType < bNodeType) return -1; + if (aNodeType > bNodeType) return 1; + + const aNodeName = apiNodeA.api.name || ''; + const bNodeName = apiNodeB.api.name || ''; + + if (aNodeName < bNodeName) return -1; + if (aNodeName > bNodeName) return 1; + return 0; +}