Enhance Docusaurus Documentation with SDK Code Snippets using liblab's Enhanced API Spec
This tutorial will guide you through integrating the enhanced OpenAPI spec generated by liblab into your Docusaurus documentation site. By using liblab, you'll be able to improve your API reference pages with SDK code snippets and examples across multiple programming languages, improving the overall developer experience.
Following these steps, you'll create a new Docusaurus project, configure API reference pages, generate an enhanced OpenAPI spec, and customize the Docusaurus components to include SDK snippets.
Prerequisites
Before you start, ensure you have:
- A liblab account.
- The liblab CLI installed.
Steps
- Create a New Docusaurus Project
- Create the API Reference Pages
- Create the liblab Project
- Enhance the OpenAPI
- Create a Wrapper for the API Components
- Test the SDKs Documentation
1. Create a New Docusaurus Project
To set up your documentation project, use the Docusaurus CLI and the liblab template:
-
Run the following command to create the project. Replace
<your-project-name>
with the desired name of your documentation project:npx [email protected] <your-project-name> --package-manager yarn
-
When prompted, select
Git repository
as the template source, then provide this GitHub repository URL as the template:https://github.com/liblaber/docusaurus-liblab-template.git
-
For the cloning option, choose
Copy: do a shallow clone, but do not create a git repo
. -
After cloning, navigate to the project directory and install dependencies with:
cd <your-project-name>
npm install -
To preview your project locally, run:
npm run start
Once started, the project will open in your browser at http://localhost:3000/. You'll see an initial preview, as shown in the image below.
2. Create the API Reference Pages
Now, you'll add API content to the documentation site. The template from Step 1 includes the Docusaurus OpenAPI plugin, which enables you to generate API documentation from an OpenAPI spec.
2.1 Add the OpenAPI Spec
For this tutorial, we'll use the PetStore API, but you can substitute it with your own API spec.
-
Create a directory called
static/api-spec
in the project root and add anopenapi.json
file with the PetStore OpenAPI Spec or your own OpenAPI spec. -
Open
docusaurus.config.js
and update it to use theopenapi.json
file to generate the endpoint pages. Setpetstore.specPath
to./static/api-spec/openapi.json
as shown:docusaurus.config.jsplugins: [
[
"docusaurus-plugin-openapi-docs",
{
id: "openapi",
docsPluginId: "classic",
config: {
petstore: {
specPath: "./static/api-spec/openapi.json",
outputDir: "docs/petstore",
sidebarOptions: {
groupPathsBy: "tag",
categoryLinkSource: "tag",
},
} satisfies OpenApiPlugin.Options,
} satisfies Plugin.PluginOptions,
},
],
],
This configuration tells the plugin to generate API docs in the docs/petstore
directory.
2.2 Generate the API Reference Pages
To create the API endpoint pages in the documentation, run these commands from the root of your project:
npm run docusaurus clean-api-docs all
npm run docusaurus gen-api-docs all
These commands generate a new petstore
folder in the docs
directory, containing documentation pages for each endpoint. Below is an example of the new file structure:
├── docs/
├── intro.md
├── petstore/
│ ├── add-pet.api.mdx
│ ├── create-user.api.mdx
│ ├── create-users-with-list-input.api.mdx
│ ├── delete-order.api.mdx
│ ├── delete-pet.api.mdx
│ ├── delete-user.api.mdx
│ ├── find-pets-by-status.api.mdx
│ ├── find-pets-by-tags.api.mdx
│ ├── get-inventory.api.mdx
│ ├── get-order-by-id.api.mdx
│ ├── get-pet-by-id.api.mdx
│ ├── get-user-by-name.api.mdx
│ ├── login-user.api.mdx
│ ├── logout-user.api.mdx
│ ├── place-order.api.mdx
│ ├── pet.tag.mdx
│ ├── sidebar.ts
│ ├── store.tag.mdx
│ ├── swagger-petstore-openapi-3-0.info.mdx
│ ├── update-pet.api.mdx
│ ├── update-pet-with-form.api.mdx
│ ├── update-user.api.mdx
│ ├── upload-file.api.mdx
│ └── user.tag.mdx
├── tutorial-basics/
└── tutorial-extras/
2.3 Update the Documentation Structure
To make the API endpoint pages accessible, you'll need to update sidebars.ts
and docusaurus.config.ts
:
Edit the sidebars.ts
file at the project root to include the PetStore endpoints. A file with the sidebar structure for the Petstore documentation was generated in Step 2.2. You have to reference it in sidebars.ts
as shown:
import type { SidebarsConfig } from "@docusaurus/plugin-content-docs";
const sidebars: SidebarsConfig = {
tutorialSidebar: [
{ type: "doc", id: "intro" },
{ type: "autogenerated", dirName: "tutorial-basics" },
{ type: "autogenerated", dirName: "tutorial-extras" },
],
openApiSidebar: [
{
type: "category",
label: "Petstore",
link: {
type: "generated-index",
title: "Petstore API",
slug: "/category/petstore-api",
},
items: require("./docs/petstore/sidebar.js"),
},
],
};
export default sidebars;
-
Add a new tab to the navbar for the PetStore API reference. In
docusaurus.config.ts
, add a new item undernavbar
:docusaurus.config.tsnavbar: {
...
items: [
{
type: "doc",
docId: "intro",
position: "left",
label: "Tutorial",
},
{ to: "/blog", label: "Blog", position: "left" },
{
label: "Petstore API",
position: "left",
to: "/docs/category/petstore-api",
},
{
href: "https://github.com/facebook/docusaurus",
label: "GitHub",
position: "right",
},
],
}, -
Save your changes and restart the documentation project:
Terminalnpm run start
After restarting, access your documentation at http://localhost:3000/. A new Petstore API tab will appear in the navbar. Select it to view the API docs in the sidebar. Expand the Petstore section to reveal an introduction page and documentation for each endpoint/HTTP method combination.
These generated pages are based on the PetStore OpenAPI spec. The Introduction
page displays the info/description
from the spec. Code snippets on the right show examples in various programming languages for calling the API.
The code snippets will be replaced with enhanced OpenAPI content when the liblab integration is complete.
3. Create the liblab Project
Now, you'll use liblab to create an enhanced version of your OpenAPI spec. liblab generates SDKs based on your OpenAPI spec and improves it with features like code snippets and SDK examples, enhancing the developer experience with more clear documentation. This enhanced OpenAPI spec will be used to generate a new version of the endpoint documentation pages created in Step 2.
Access the API and SDK documentation overview for more details on how liblab enhances the OpenAPI spec.
To begin, initialize a liblab project in a new directory using the following command:
liblab init
A new liblab.config.json
file will be created in the directory. Update this file to reference the same OpenAPI spec used in Step 2.1. For this tutorial, we'll continue using the PetStore API:
{
"sdkName": "petstore",
"apiName": "petstore",
"specFilePath": "https://petstore3.swagger.io/api/v3/openapi.json"
...
}
By default, liblab generates SDKs in the following programming languages when you initialize a new project:
- Java
- Go
- Python
- TypeScript
You can customize this list of languages in your liblab.config.json
file. The languages specified here will determine which SDK code snippets are included in the enhanced OpenAPI spec. This enhanced spec will combine your OpenAPI definition with code snippets for each specified language, making it easier for developers to use the API.
Locate the languageOptions
section in liblab.config.json
to adjust the languages. Remove any configurations for languages you don't need. Check the liblab config file documentation for additional configuration options.
As the last configuration step, ensure liblab is set to generate the enhanced OpenAPI spec by adding the following configuration to liblab.config.json
:
{
...
"docs": [
"enhancedApiSpec"
],
...
}
For more details on the configuration options and structure used by the enhanced OpenAPI spec generated by liblab, access the API and SDK documentation page.
4. Enhance the OpenAPI
Once you've created the liblab project, build it to generate the SDKs and the enhanced OpenAPI spec. Run the following command:
liblab build -y
liblab will generate the SDKs and the enhanced OpenAPI spec, which you'll find in the output
directory.
Copy the content from the enhanced OpenAPI file (enhancedApiSpec.json
) and replace the content of the static/api-spec/openapi.json
file created in Step 2.1. This updated OpenAPI file now includes SDK code snippets and examples.
To display this enhanced content in your documentation portal, you must update the components that render the OpenAPI spec. The next section addresses this process.
5. Create a Wrapper for the API Components
Docusaurus is built on React, a component-based library for creating websites. Its UI consists of components that can be styled, nested, and customized.
To use the enhanced OpenAPI spec in Docusaurus, you'll need to “swizzle” two components:
APIExplorer/CodeSnippets
to incorporate the SDK code snippets generated by liblab directly into your documentation.APIExplorer/CodeTabs
to control and restrict the displayed programming languages to only those generated with liblab SDKs.
Swizzling is a Docusaurus feature that lets you wrap or replace components. For more details, access the Docusaurus documentation on swizzling.
5.1 Swizzle the CodeSnippets Component
To swizzle the APIExplorer/CodeSnippets
component, follow these steps:
-
In the root of your documentation project, run the following command:
npm run swizzle docusaurus-theme-openapi-docs ApiExplorer/CodeSnippets -- --wrap --typescript
-
When prompted, select
YES: I know what I am doing!
:? Do you really want to swizzle this unsafe internal component?
› - Use arrow-keys. Return to submit.
NO: cancel and stay safe
❯ YES: I know what I am doing!
[Exit]This creates a wrapper for the component in the
src/theme
folder:src
└── theme
└── APIExplorer
└── CodeSnippets
└── index.tsx -
Replace the content in the
index.tsx
file with the following:src/theme/APIExplorer/CodeSnippets/index.tsximport React, { useState, useEffect } from "react";
import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
import ApiCodeBlock from "@theme/ApiExplorer/ApiCodeBlock";
import CodeTabs from "@theme/ApiExplorer/CodeTabs";
import buildPostmanRequest from "@theme/ApiExplorer/buildPostmanRequest";
import { useTypedSelector } from "@theme/ApiItem/hooks";
import codegen from "postman-code-generators";
import sdk from "postman-collection";
import { CodeSampleLanguage } from "./code-snippets-types";
export interface CodeSample {
source: string;
lang: CodeSampleLanguage;
label?: string;
}
export interface Language {
highlight: string;
language: string;
codeSampleLanguage?: string;
logoClass: string;
options: {
followRedirect?: boolean;
trimRequestBody?: boolean;
longFormat?: boolean;
ES6_enabled?: boolean;
};
variant: string;
variants: string[];
enabled?: boolean;
}
export interface Props {
codeSamples: CodeSample[];
postman?: sdk.Request;
}
function CodeTab({ children, hidden, className }: any): JSX.Element {
return (
<div role="tabpanel" className={className} {...{ hidden }}>
{children}
</div>
);
}
// Default language configurations from original script
const defaultLanguages: Language[] = [
{
highlight: "bash",
language: "curl",
codeSampleLanguage: "bash",
logoClass: "bash",
options: {
longFormat: false,
followRedirect: true,
trimRequestBody: true,
},
variant: "cURL",
variants: ["curl"],
enabled: true,
},
{
highlight: "ruby",
language: "ruby",
codeSampleLanguage: "Ruby",
logoClass: "ruby",
options: {
followRedirect: true,
trimRequestBody: true,
},
variant: "Net::HTTP",
variants: ["net::http"],
enabled: false
},
{
highlight: "powershell",
language: "powershell",
codeSampleLanguage: "PowerShell",
logoClass: "powershell",
options: {
followRedirect: true,
trimRequestBody: true,
},
variant: "RestMethod",
variants: ["restmethod"],
enabled: false
},
];
function CodeSnippets({ codeSamples, postman }: Props) {
const { siteConfig } = useDocusaurusContext();
const validCodeSamples = codeSamples.filter((sample) => sample.lang);
const [selectedSample, setSelectedSample] = useState<string | undefined>(
validCodeSamples[0]?.lang
);
const [generatedCode, setGeneratedCode] = useState<Record<string, string>>({});
// Get theme configuration for languages
const userDefinedLanguages =
(siteConfig?.themeConfig?.languageTabs as Language[] | undefined) ?? [];
// Merge default and user-defined languages
const mergedLanguages = defaultLanguages.map(defaultLang => {
const userLang = userDefinedLanguages.find(
lang => lang.language === defaultLang.language
);
// If user has defined this language, merge its properties
if (userLang) {
return {
...defaultLang,
...userLang,
options: { ...defaultLang.options, ...userLang.options },
enabled: userLang.enabled ?? defaultLang.enabled
};
}
return defaultLang;
});
// Add any additional user-defined languages
const additionalLanguages = userDefinedLanguages.filter(
lang => !defaultLanguages.some(defaultLang => defaultLang.language === lang.language)
);
// Combine all languages and filter enabled ones
const activeLanguages = [...mergedLanguages, ...additionalLanguages]
.filter(lang => lang.enabled !== false);
// Get required state from Redux store if postman is provided
const contentType = useTypedSelector((state: any) => state.contentType.value);
const accept = useTypedSelector((state: any) => state.accept.value);
const server = useTypedSelector((state: any) => state.server.value);
const body = useTypedSelector((state: any) => state.body);
const pathParams = useTypedSelector((state: any) => state.params.path);
const queryParams = useTypedSelector((state: any) => state.params.query);
const cookieParams = useTypedSelector((state: any) => state.params.cookie);
const headerParams = useTypedSelector((state: any) => state.params.header);
const auth = useTypedSelector((state: any) => state.auth);
// Generate code for configured languages if postman request is provided
useEffect(() => {
if (postman) {
const postmanRequest = buildPostmanRequest(postman, {
queryParams,
pathParams,
cookieParams,
contentType,
accept,
headerParams,
body,
server,
auth,
});
// Generate code for each enabled language
activeLanguages.forEach((lang) => {
codegen.convert(
lang.language,
lang.variant,
postmanRequest,
lang.options,
(error: any, snippet: string) => {
if (!error) {
setGeneratedCode(prev => ({
...prev,
[lang.language]: snippet
}));
}
}
);
});
}
}, [
postman,
activeLanguages,
queryParams,
pathParams,
cookieParams,
contentType,
accept,
headerParams,
body,
server,
auth,
]);
if (validCodeSamples.length === 0 && !postman) {
return null;
}
// Combine custom code samples with generated code
const generatedSamples = postman
? Object.entries(generatedCode).map(([language, source]) => {
const langConfig = activeLanguages.find(l => l.language === language);
return {
lang: language as CodeSampleLanguage,
source,
label: langConfig?.codeSampleLanguage || langConfig?.variant || language
};
})
: [];
const allSamples = [...generatedSamples, ...validCodeSamples];
return (
<>
<CodeTabs
groupId="code-samples"
action={{
setSelectedSample: setSelectedSample,
}}
languageSet={allSamples.map((sample) => ({
language: sample.lang,
label: sample.label || sample.lang,
}))}
lazy
>
{allSamples.map((sample) => (
<CodeTab
value={sample.lang}
label={sample.label || sample.lang}
key={sample.lang}
attributes={{
className: `openapi-tabs__code-item--${sample.lang
.toLowerCase()
.replace("#", "sharp")
.replace("curl", "bash")
.replace("typescript", "nodejs")
}`,
}}
>
<ApiCodeBlock
language={sample.lang.toLocaleLowerCase()}
className="openapi-explorer__code-block"
showLineNumbers={true}
>
{sample.source}
</ApiCodeBlock>
</CodeTab>
))}
</CodeTabs>
</>
);
}
export default CodeSnippets; -
Create three additional files in the
CodeSnippets/
directory to support the customized component. Copy and paste the following content into each respective file:- code-snippets-types.ts
- languages.json
- languages.ts
code-snippets-types.ts// https://github.com/github-linguist/linguist/blob/master/lib/linguist/popular.yml
export type CodeSampleLanguage =
| "C"
| "C#"
| "C++"
| "CoffeeScript"
| "CSS"
| "Dart"
| "DM"
| "Elixir"
| "Go"
| "Groovy"
| "HTML"
| "Java"
| "JavaScript"
| "Kotlin"
| "Objective-C"
| "Perl"
| "PHP"
| "PowerShell"
| "Python"
| "Ruby"
| "Rust"
| "Scala"
| "Shell"
| "Swift"
| "TypeScript";
export interface Language {
highlight: string;
language: string;
codeSampleLanguage: CodeSampleLanguage;
logoClass: string;
variant: string;
variants: string[];
options?: { [key: string]: boolean };
sample?: string;
samples?: string[];
samplesSources?: string[];
samplesLabels?: string[];
}
// https://redocly.com/docs/api-reference-docs/specification-extensions/x-code-samples
export interface CodeSample {
source: string;
lang: CodeSampleLanguage;
label?: string;
}languages.json[
{
"key": "csharp",
"label": "C#",
"syntax_mode": "csharp",
"variants": [
{
"key": "RestSharp",
"options": [
{
"name": "Include boilerplate",
"id": "includeBoilerplate",
"type": "boolean",
"default": false,
"description": "Include class definition and import statements in snippet"
},
{
"name": "Set indentation count",
"id": "indentCount",
"type": "positiveInteger",
"default": 2,
"description": "Set the number of indentation characters to add per code level"
},
{
"name": "Set indentation type",
"id": "indentType",
"type": "enum",
"availableOptions": ["Tab", "Space"],
"default": "Space",
"description": "Select the character used to indent lines of code"
},
{
"name": "Set request timeout",
"id": "requestTimeout",
"type": "positiveInteger",
"default": 0,
"description": "Set number of milliseconds the request should wait for a response before timing out (use 0 for infinity)"
},
{
"name": "Follow redirects",
"id": "followRedirect",
"type": "boolean",
"default": true,
"description": "Automatically follow HTTP redirects"
},
{
"name": "Trim request body fields",
"id": "trimRequestBody",
"type": "boolean",
"default": false,
"description": "Remove white space and additional lines that may affect the server's response"
}
]
}
]
},
{
"key": "curl",
"label": "cURL",
"syntax_mode": "powershell",
"variants": [
{
"key": "cURL",
"options": [
{
"name": "Generate multiline snippet",
"id": "multiLine",
"type": "boolean",
"default": true,
"description": "Split cURL command across multiple lines"
},
{
"name": "Use long form options",
"id": "longFormat",
"type": "boolean",
"default": true,
"description": "Use the long form for cURL options (--header instead of -H)"
},
{
"name": "Line continuation character",
"id": "lineContinuationCharacter",
"availableOptions": ["\\", "^", "`"],
"type": "enum",
"default": "\\",
"description": "Set a character used to mark the continuation of a statement on the next line (generally, \\ for OSX/Linux, ^ for Windows cmd and ` for Powershell)"
},
{
"name": "Quote Type",
"id": "quoteType",
"availableOptions": ["single", "double"],
"type": "enum",
"default": "single",
"description": "String denoting the quote type to use (single or double) for URL (Use double quotes when running curl in cmd.exe and single quotes for the rest)"
},
{
"name": "Set request timeout (in seconds)",
"id": "requestTimeoutInSeconds",
"type": "positiveInteger",
"default": 0,
"description": "Set number of seconds the request should wait for a response before timing out (use 0 for infinity)"
},
{
"name": "Follow redirects",
"id": "followRedirect",
"type": "boolean",
"default": true,
"description": "Automatically follow HTTP redirects"
},
{
"name": "Trim request body fields",
"id": "trimRequestBody",
"type": "boolean",
"default": false,
"description": "Remove white space and additional lines that may affect the server's response"
},
{
"name": "Use Silent Mode",
"id": "silent",
"type": "boolean",
"default": false,
"description": "Display the requested data without showing the cURL progress meter or error messages"
}
]
}
]
},
{
"key": "dart",
"label": "Dart",
"syntax_mode": "dart",
"variants": [
{
"key": "http",
"options": [
{
"name": "Set indentation count",
"id": "indentCount",
"type": "positiveInteger",
"default": 2,
"description": "Set the number of indentation characters to add per code level"
},
{
"name": "Set indentation type",
"id": "indentType",
"type": "enum",
"availableOptions": ["Tab", "Space"],
"default": "Space",
"description": "Select the character used to indent lines of code"
},
{
"name": "Set request timeout",
"id": "requestTimeout",
"type": "positiveInteger",
"default": 0,
"description": "Set number of milliseconds the request should wait for a response before timing out (use 0 for infinity)"
},
{
"name": "Trim request body fields",
"id": "trimRequestBody",
"type": "boolean",
"default": false,
"description": "Remove white space and additional lines that may affect the server's response"
},
{
"name": "Include boilerplate",
"id": "includeBoilerplate",
"type": "boolean",
"default": false,
"description": "Include class definition and import statements in snippet"
},
{
"name": "Follow redirects",
"id": "followRedirect",
"type": "boolean",
"default": true,
"description": "Automatically follow HTTP redirects"
}
]
}
]
},
{
"key": "go",
"label": "Go",
"syntax_mode": "golang",
"variants": [
{
"key": "Native",
"options": [
{
"name": "Set indentation count",
"id": "indentCount",
"type": "positiveInteger",
"default": 2,
"description": "Set the number of indentation characters to add per code level"
},
{
"name": "Set indentation type",
"id": "indentType",
"type": "enum",
"availableOptions": ["Tab", "Space"],
"default": "Space",
"description": "Select the character used to indent lines of code"
},
{
"name": "Set request timeout",
"id": "requestTimeout",
"type": "positiveInteger",
"default": 0,
"description": "Set number of milliseconds the request should wait for a response before timing out (use 0 for infinity)"
},
{
"name": "Follow redirects",
"id": "followRedirect",
"type": "boolean",
"default": true,
"description": "Automatically follow HTTP redirects"
},
{
"name": "Trim request body fields",
"id": "trimRequestBody",
"type": "boolean",
"default": false,
"description": "Remove white space and additional lines that may affect the server's response"
}
]
}
]
},
{
"key": "http",
"label": "HTTP",
"syntax_mode": "text",
"variants": [
{
"key": "HTTP",
"options": [
{
"name": "Trim request body fields",
"id": "trimRequestBody",
"type": "boolean",
"default": false,
"description": "Remove white space and additional lines that may affect the server's response"
}
]
}
]
},
{
"key": "java",
"label": "Java",
"syntax_mode": "java",
"variants": [
{
"key": "OkHttp",
"options": [
{
"name": "Include boilerplate",
"id": "includeBoilerplate",
"type": "boolean",
"default": false,
"description": "Include class definition and import statements in snippet"
},
{
"name": "Set indentation count",
"id": "indentCount",
"type": "positiveInteger",
"default": 2,
"description": "Set the number of indentation characters to add per code level"
},
{
"name": "Set indentation type",
"id": "indentType",
"type": "enum",
"availableOptions": ["Tab", "Space"],
"default": "Space",
"description": "Select the character used to indent lines of code"
},
{
"name": "Set request timeout",
"id": "requestTimeout",
"type": "positiveInteger",
"default": 0,
"description": "Set number of milliseconds the request should wait for a response before timing out (use 0 for infinity)"
},
{
"name": "Follow redirects",
"id": "followRedirect",
"type": "boolean",
"default": true,
"description": "Automatically follow HTTP redirects"
},
{
"name": "Trim request body fields",
"id": "trimRequestBody",
"type": "boolean",
"default": false,
"description": "Remove white space and additional lines that may affect the server's response"
}
]
},
{
"key": "Unirest",
"options": [
{
"name": "Include boilerplate",
"id": "includeBoilerplate",
"type": "boolean",
"default": false,
"description": "Include class definition and import statements in snippet"
},
{
"name": "Set indentation count",
"id": "indentCount",
"type": "positiveInteger",
"default": 2,
"description": "Set the number of indentation characters to add per code level"
},
{
"name": "Set indentation type",
"id": "indentType",
"type": "enum",
"availableOptions": ["Tab", "Space"],
"default": "Space",
"description": "Select the character used to indent lines of code"
},
{
"name": "Set request timeout",
"id": "requestTimeout",
"type": "positiveInteger",
"default": 0,
"description": "Set number of milliseconds the request should wait for a response before timing out (use 0 for infinity)"
},
{
"name": "Follow redirects",
"id": "followRedirect",
"type": "boolean",
"default": true,
"description": "Automatically follow HTTP redirects"
},
{
"name": "Trim request body fields",
"id": "trimRequestBody",
"type": "boolean",
"default": false,
"description": "Remove white space and additional lines that may affect the server's response"
}
]
}
]
},
{
"key": "javascript",
"label": "JavaScript",
"syntax_mode": "javascript",
"variants": [
{
"key": "Fetch",
"options": [
{
"name": "Set indentation count",
"id": "indentCount",
"type": "positiveInteger",
"default": 2,
"description": "Set the number of indentation characters to add per code level"
},
{
"name": "Set indentation type",
"id": "indentType",
"type": "enum",
"availableOptions": ["Tab", "Space"],
"default": "Space",
"description": "Select the character used to indent lines of code"
},
{
"name": "Set request timeout",
"id": "requestTimeout",
"type": "positiveInteger",
"default": 0,
"description": "Set number of milliseconds the request should wait for a response before timing out (use 0 for infinity)"
},
{
"name": "Follow redirects",
"id": "followRedirect",
"type": "boolean",
"default": true,
"description": "Automatically follow HTTP redirects"
},
{
"name": "Trim request body fields",
"id": "trimRequestBody",
"type": "boolean",
"default": false,
"description": "Remove white space and additional lines that may affect the server's response"
}
]
},
{
"key": "jQuery",
"options": [
{
"name": "Set indentation count",
"id": "indentCount",
"type": "positiveInteger",
"default": 2,
"description": "Set the number of indentation characters to add per code level"
},
{
"name": "Set indentation type",
"id": "indentType",
"type": "enum",
"availableOptions": ["Tab", "Space"],
"default": "Space",
"description": "Select the character used to indent lines of code"
},
{
"name": "Set request timeout",
"id": "requestTimeout",
"type": "positiveInteger",
"default": 0,
"description": "Set number of milliseconds the request should wait for a response before timing out (use 0 for infinity)"
},
{
"name": "Trim request body fields",
"id": "trimRequestBody",
"type": "boolean",
"default": false,
"description": "Remove white space and additional lines that may affect the server's response"
}
]
},
{
"key": "XHR",
"options": [
{
"name": "Set indentation count",
"id": "indentCount",
"type": "positiveInteger",
"default": 2,
"description": "Set the number of indentation characters to add per code level"
},
{
"name": "Set indentation type",
"id": "indentType",
"type": "enum",
"availableOptions": ["Tab", "Space"],
"default": "Space",
"description": "Select the character used to indent lines of code"
},
{
"name": "Set request timeout",
"id": "requestTimeout",
"type": "positiveInteger",
"default": 0,
"description": "Set number of milliseconds the request should wait for a response before timing out (use 0 for infinity)"
},
{
"name": "Trim request body fields",
"id": "trimRequestBody",
"type": "boolean",
"default": false,
"description": "Remove white space and additional lines that may affect the server's response"
}
]
}
]
},
{
"key": "c",
"label": "C",
"syntax_mode": "c_cpp",
"variants": [
{
"key": "libcurl",
"options": [
{
"name": "Include boilerplate",
"id": "includeBoilerplate",
"type": "boolean",
"default": false,
"description": "Include class definition and import statements in snippet"
},
{
"name": "Protocol",
"id": "protocol",
"type": "enum",
"availableOptions": ["http", "https"],
"default": "https",
"description": "The protocol to be used to make the request"
},
{
"name": "Set indentation count",
"id": "indentCount",
"type": "positiveInteger",
"default": 2,
"description": "Set the number of indentation characters to add per code level"
},
{
"name": "Set indentation type",
"id": "indentType",
"type": "enum",
"availableOptions": ["Tab", "Space"],
"default": "Space",
"description": "Select the character used to indent lines of code"
},
{
"name": "Follow redirects",
"id": "followRedirect",
"type": "boolean",
"default": true,
"description": "Automatically follow HTTP redirects"
},
{
"name": "Trim request body fields",
"id": "trimRequestBody",
"type": "boolean",
"default": false,
"description": "Remove white space and additional lines that may affect the server's response"
},
{
"name": "Use curl_mime",
"id": "useMimeType",
"type": "boolean",
"default": true,
"description": "Use curl_mime to send multipart/form-data requests"
},
{
"name": "Set request timeout",
"id": "requestTimeout",
"type": "positiveInteger",
"default": 0,
"description": "Set number of milliseconds the request should wait for a response before timing out (use 0 for infinity)"
}
]
}
]
},
{
"key": "nodejs",
"label": "NodeJs",
"syntax_mode": "javascript",
"variants": [
{
"key": "Axios",
"options": [
{
"name": "Set indentation count",
"id": "indentCount",
"type": "positiveInteger",
"default": 2,
"description": "Set the number of indentation characters to add per code level"
},
{
"name": "Set indentation type",
"id": "indentType",
"type": "enum",
"availableOptions": ["Tab", "Space"],
"default": "Space",
"description": "Select the character used to indent lines of code"
},
{
"name": "Set request timeout",
"id": "requestTimeout",
"type": "positiveInteger",
"default": 0,
"description": "Set number of milliseconds the request should wait for a response before timing out (use 0 for infinity)"
},
{
"name": "Follow redirects",
"id": "followRedirect",
"type": "boolean",
"default": true,
"description": "Automatically follow HTTP redirects"
},
{
"name": "Trim request body fields",
"id": "trimRequestBody",
"type": "boolean",
"default": false,
"description": "Remove white space and additional lines that may affect the server's response"
},
{
"name": "Enable ES6 features",
"id": "ES6_enabled",
"type": "boolean",
"default": false,
"description": "Modifies code snippet to incorporate ES6 (EcmaScript) features"
}
]
},
{
"key": "Native",
"options": [
{
"name": "Set indentation count",
"id": "indentCount",
"type": "positiveInteger",
"default": 2,
"description": "Set the number of indentation characters to add per code level"
},
{
"name": "Set indentation type",
"id": "indentType",
"type": "enum",
"availableOptions": ["Tab", "Space"],
"default": "Space",
"description": "Select the character used to indent lines of code"
},
{
"name": "Set request timeout",
"id": "requestTimeout",
"type": "positiveInteger",
"default": 0,
"description": "Set number of milliseconds the request should wait for a response before timing out (use 0 for infinity)"
},
{
"name": "Follow redirects",
"id": "followRedirect",
"type": "boolean",
"default": true,
"description": "Automatically follow HTTP redirects"
},
{
"name": "Trim request body fields",
"id": "trimRequestBody",
"type": "boolean",
"default": false,
"description": "Remove white space and additional lines that may affect the server's response"
},
{
"name": "Enable ES6 features",
"id": "ES6_enabled",
"type": "boolean",
"default": false,
"description": "Modifies code snippet to incorporate ES6 (EcmaScript) features"
}
]
}
]
},
{
"key": "objective-c",
"label": "Objective-C",
"syntax_mode": "objectivec",
"variants": [
{
"key": "NSURLSession",
"options": [
{
"name": "Set indentation count",
"id": "indentCount",
"type": "positiveInteger",
"default": 2,
"description": "Set the number of indentation characters to add per code level"
},
{
"name": "Set indentation type",
"id": "indentType",
"type": "enum",
"availableOptions": ["Tab", "Space"],
"default": "Space",
"description": "Select the character used to indent lines of code"
},
{
"name": "Set request timeout",
"id": "requestTimeout",
"type": "positiveInteger",
"default": 10000,
"description": "Set number of milliseconds the request should wait for a response before timing out (use 0 for infinity)"
},
{
"name": "Trim request body fields",
"id": "trimRequestBody",
"type": "boolean",
"default": false,
"description": "Remove white space and additional lines that may affect the server's response"
},
{
"name": "Include boilerplate",
"id": "includeBoilerplate",
"type": "boolean",
"default": false,
"description": "Include class definition and import statements in snippet"
}
]
}
]
},
{
"key": "ocaml",
"label": "OCaml",
"syntax_mode": "ocaml",
"variants": [
{
"key": "Cohttp",
"options": [
{
"name": "Set indentation count",
"id": "indentCount",
"type": "positiveInteger",
"default": 2,
"description": "Set the number of indentation characters to add per code level"
},
{
"name": "Set indentation type",
"id": "indentType",
"type": "enum",
"availableOptions": ["Tab", "Space"],
"default": "Space",
"description": "Select the character used to indent lines of code"
},
{
"name": "Trim request body fields",
"id": "trimRequestBody",
"type": "boolean",
"default": false,
"description": "Remove white space and additional lines that may affect the server's response"
},
{
"name": "Set request timeout",
"id": "requestTimeout",
"type": "positiveInteger",
"default": 0,
"description": "Set number of milliseconds the request should wait for a response before timing out (use 0 for infinity)"
},
{
"name": "Follow redirects",
"id": "followRedirect",
"type": "boolean",
"default": true,
"description": "Automatically follow HTTP redirects"
}
]
}
]
},
{
"key": "php",
"label": "PHP",
"syntax_mode": "php",
"variants": [
{
"key": "cURL",
"options": [
{
"name": "Set indentation count",
"id": "indentCount",
"type": "positiveInteger",
"default": 2,
"description": "Set the number of indentation characters to add per code level"
},
{
"name": "Set indentation type",
"id": "indentType",
"type": "enum",
"availableOptions": ["Tab", "Space"],
"default": "Space",
"description": "Select the character used to indent lines of code"
},
{
"name": "Set request timeout",
"id": "requestTimeout",
"type": "positiveInteger",
"default": 0,
"description": "Set number of milliseconds the request should wait for a response before timing out (use 0 for infinity)"
},
{
"name": "Follow redirects",
"id": "followRedirect",
"type": "boolean",
"default": true,
"description": "Automatically follow HTTP redirects"
},
{
"name": "Trim request body fields",
"id": "trimRequestBody",
"type": "boolean",
"default": false,
"description": "Remove white space and additional lines that may affect the server's response"
}
]
},
{
"key": "Guzzle",
"options": [
{
"name": "Set indentation count",
"id": "indentCount",
"type": "positiveInteger",
"default": 2,
"description": "Set the number of indentation characters to add per code level"
},
{
"name": "Set indentation type",
"id": "indentType",
"type": "enum",
"availableOptions": ["Tab", "Space"],
"default": "Space",
"description": "Select the character used to indent lines of code"
},
{
"name": "Trim request body fields",
"id": "trimRequestBody",
"type": "boolean",
"default": false,
"description": "Remove white space and additional lines that may affect the server's response"
},
{
"name": "Set request timeout",
"id": "requestTimeout",
"type": "positiveInteger",
"default": 0,
"description": "Set number of milliseconds the request should wait for a response before timing out (use 0 for infinity)"
},
{
"name": "Follow redirects",
"id": "followRedirect",
"type": "boolean",
"default": true,
"description": "Automatically follow HTTP redirects"
},
{
"name": "Set communication type",
"id": "asyncType",
"type": "enum",
"availableOptions": ["async", "sync"],
"default": "async",
"description": "Set if the requests will be asynchronous or synchronous"
},
{
"name": "Include boilerplate",
"id": "includeBoilerplate",
"type": "boolean",
"default": false,
"description": "Include class definition and import statements in snippet"
}
]
},
{
"key": "HTTP_Request2",
"options": [
{
"name": "Set indentation count",
"id": "indentCount",
"type": "positiveInteger",
"default": 2,
"description": "Set the number of indentation characters to add per code level"
},
{
"name": "Set indentation type",
"id": "indentType",
"type": "enum",
"default": "Space",
"availableOptions": ["Tab", "Space"],
"description": "Select the character used to indent lines of code"
},
{
"name": "Set request timeout",
"id": "requestTimeout",
"type": "positiveInteger",
"default": 0,
"description": "Set number of milliseconds the request should wait for a response before timing out (use 0 for infinity)"
},
{
"name": "Trim request body fields",
"id": "trimRequestBody",
"type": "boolean",
"default": false,
"description": "Remove white space and additional lines that may affect the server's response"
},
{
"name": "Follow redirects",
"id": "followRedirect",
"type": "boolean",
"default": true,
"description": "Automatically follow HTTP redirects"
}
]
},
{
"key": "pecl_http",
"options": [
{
"name": "Set indentation count",
"id": "indentCount",
"type": "positiveInteger",
"default": 2,
"description": "Set the number of indentation characters to add per code level"
},
{
"name": "Set indentation type",
"id": "indentType",
"type": "enum",
"default": "Space",
"availableOptions": ["Tab", "Space"],
"description": "Select the character used to indent lines of code"
},
{
"name": "Set request timeout",
"id": "requestTimeout",
"type": "positiveInteger",
"default": 0,
"description": "Set number of milliseconds the request should wait for a response before timing out (use 0 for infinity)"
},
{
"name": "Trim request body fields",
"id": "trimRequestBody",
"type": "boolean",
"default": false,
"description": "Remove white space and additional lines that may affect the server's response"
},
{
"name": "Follow redirects",
"id": "followRedirect",
"type": "boolean",
"default": true,
"description": "Automatically follow HTTP redirects"
}
]
}
]
},
{
"key": "powershell",
"label": "PowerShell",
"syntax_mode": "powershell",
"variants": [
{
"key": "RestMethod",
"options": [
{
"name": "Set request timeout",
"id": "requestTimeout",
"type": "positiveInteger",
"default": 0,
"description": "Set number of milliseconds the request should wait for a response before timing out (use 0 for infinity)"
},
{
"name": "Follow redirects",
"id": "followRedirect",
"type": "boolean",
"default": true,
"description": "Automatically follow HTTP redirects"
},
{
"name": "Trim request body fields",
"id": "trimRequestBody",
"type": "boolean",
"default": false,
"description": "Remove white space and additional lines that may affect the server's response"
}
]
}
]
},
{
"key": "python",
"label": "Python",
"syntax_mode": "python",
"variants": [
{
"key": "http.client",
"options": [
{
"name": "Set indentation count",
"id": "indentCount",
"type": "positiveInteger",
"default": 2,
"description": "Set the number of indentation characters to add per code level"
},
{
"name": "Set indentation type",
"id": "indentType",
"type": "enum",
"default": "Space",
"availableOptions": ["Tab", "Space"],
"description": "Select the character used to indent lines of code"
},
{
"name": "Set request timeout",
"id": "requestTimeout",
"type": "positiveInteger",
"default": 0,
"description": "Set number of milliseconds the request should wait for a response before timing out (use 0 for infinity)"
},
{
"name": "Trim request body fields",
"id": "trimRequestBody",
"type": "boolean",
"default": false,
"description": "Remove white space and additional lines that may affect the server's response"
}
]
},
{
"key": "Requests",
"options": [
{
"name": "Set indentation count",
"id": "indentCount",
"type": "positiveInteger",
"default": 2,
"description": "Set the number of indentation characters to add per code level"
},
{
"name": "Set indentation type",
"id": "indentType",
"type": "enum",
"availableOptions": ["Tab", "Space"],
"default": "Space",
"description": "Select the character used to indent lines of code"
},
{
"name": "Set request timeout",
"id": "requestTimeout",
"type": "positiveInteger",
"default": 0,
"description": "Set number of milliseconds the request should wait for a response before timing out (use 0 for infinity)"
},
{
"name": "Follow redirects",
"id": "followRedirect",
"type": "boolean",
"default": true,
"description": "Automatically follow HTTP redirects"
},
{
"name": "Trim request body fields",
"id": "trimRequestBody",
"type": "boolean",
"default": false,
"description": "Remove white space and additional lines that may affect the server's response"
}
]
}
]
},
{
"key": "r",
"label": "R",
"syntax_mode": "r",
"variants": [
{
"key": "httr",
"options": [
{
"name": "Set indentation count",
"id": "indentCount",
"type": "positiveInteger",
"default": 2,
"description": "Set the number of indentation characters to add per code level"
},
{
"name": "Set indentation type",
"id": "indentType",
"type": "enum",
"availableOptions": ["Tab", "Space"],
"default": "Space",
"description": "Select the character used to indent lines of code"
},
{
"name": "Trim request body fields",
"id": "trimRequestBody",
"type": "boolean",
"default": false,
"description": "Remove white space and additional lines that may affect the server's response"
},
{
"name": "Set request timeout",
"id": "requestTimeout",
"type": "positiveInteger",
"default": 0,
"description": "Set number of milliseconds the request should wait for a response before timing out (use 0 for infinity)"
}
]
},
{
"key": "RCurl",
"options": [
{
"name": "Set indentation count",
"id": "indentCount",
"type": "positiveInteger",
"default": 2,
"description": "Set the number of indentation characters to add per code level"
},
{
"name": "Set indentation type",
"id": "indentType",
"type": "enum",
"availableOptions": ["Tab", "Space"],
"default": "Space",
"description": "Select the character used to indent lines of code"
},
{
"name": "Trim request body fields",
"id": "trimRequestBody",
"type": "boolean",
"default": false,
"description": "Remove white space and additional lines that may affect the server's response"
},
{
"name": "Set request timeout",
"id": "requestTimeout",
"type": "positiveInteger",
"default": 0,
"description": "Set number of milliseconds the request should wait for a response before timing out (use 0 for infinity)"
},
{
"name": "Follow redirects",
"id": "followRedirect",
"type": "boolean",
"default": true,
"description": "Automatically follow HTTP redirects"
},
{
"name": "Ignore warnings",
"id": "ignoreWarnings",
"type": "boolean",
"default": false,
"description": "Ignore warnings from R"
}
]
}
]
},
{
"key": "ruby",
"label": "Ruby",
"syntax_mode": "ruby",
"variants": [
{
"key": "Net::HTTP",
"options": [
{
"name": "Set indentation count",
"id": "indentCount",
"type": "positiveInteger",
"default": 2,
"description": "Set the number of indentation characters to add per code level"
},
{
"name": "Set indentation type",
"id": "indentType",
"type": "enum",
"availableOptions": ["Tab", "Space"],
"default": "Space",
"description": "Select the character used to indent lines of code"
},
{
"name": "Set request timeout",
"id": "requestTimeout",
"type": "positiveInteger",
"default": 0,
"description": "Set number of milliseconds the request should wait for a response before timing out (use 0 for infinity)"
},
{
"name": "Follow redirects",
"id": "followRedirect",
"type": "boolean",
"default": true,
"description": "Automatically follow HTTP redirects"
},
{
"name": "Trim request body fields",
"id": "trimRequestBody",
"type": "boolean",
"default": false,
"description": "Remove white space and additional lines that may affect the server's response"
}
]
}
]
},
{
"key": "shell",
"label": "Shell",
"syntax_mode": "powershell",
"variants": [
{
"key": "Httpie",
"options": [
{
"name": "Set request timeout",
"id": "requestTimeout",
"type": "positiveInteger",
"default": 0,
"description": "Set number of milliseconds the request should wait for a response before timing out (use 0 for infinity)"
},
{
"name": "Follow redirects",
"id": "followRedirect",
"type": "boolean",
"default": true,
"description": "Automatically follow HTTP redirects"
}
]
},
{
"key": "wget",
"options": [
{
"name": "Set indentation count",
"id": "indentCount",
"type": "positiveInteger",
"default": 2,
"description": "Set the number of indentation characters to add per code level"
},
{
"name": "Set indentation type",
"id": "indentType",
"type": "enum",
"availableOptions": ["Tab", "Space"],
"default": "Space",
"description": "Select the character used to indent lines of code"
},
{
"name": "Set request timeout",
"id": "requestTimeout",
"type": "positiveInteger",
"default": 0,
"description": "Set number of milliseconds the request should wait for a response before timing out (use 0 for infinity)"
},
{
"name": "Follow redirects",
"id": "followRedirect",
"type": "boolean",
"default": true,
"description": "Automatically follow HTTP redirects"
},
{
"name": "Trim request body fields",
"id": "trimRequestBody",
"type": "boolean",
"default": false,
"description": "Remove white space and additional lines that may affect the server's response"
}
]
}
]
},
{
"key": "swift",
"label": "Swift",
"syntax_mode": "swift",
"variants": [
{
"key": "URLSession",
"options": [
{
"name": "Set indentation count",
"id": "indentCount",
"type": "positiveInteger",
"default": 2,
"description": "Set the number of indentation characters to add per code level"
},
{
"name": "Set indentation type",
"id": "indentType",
"type": "enum",
"availableOptions": ["Tab", "Space"],
"default": "Space",
"description": "Select the character used to indent lines of code"
},
{
"name": "Set request timeout",
"id": "requestTimeout",
"type": "positiveInteger",
"default": 0,
"description": "Set number of milliseconds the request should wait for a response before timing out (use 0 for infinity)"
},
{
"name": "Trim request body fields",
"id": "trimRequestBody",
"type": "boolean",
"default": false,
"description": "Remove white space and additional lines that may affect the server's response"
},
{
"name": "Follow redirects",
"id": "followRedirect",
"type": "boolean",
"default": true,
"description": "Automatically follow HTTP redirects"
}
]
}
]
}
]languages.tsimport find from "lodash/find";
import isArray from "lodash/isArray";
import mergeWith from "lodash/mergeWith";
import unionBy from "lodash/unionBy";
import { CodeSample, Language } from "./code-snippets-types";
export function mergeCodeSampleLanguage(
languages: Language[],
codeSamples: CodeSample[]
): Language[] {
return languages.map((language) => {
const languageCodeSamples = codeSamples.filter(
({ lang }) => lang === language.codeSampleLanguage
);
if (languageCodeSamples.length) {
const samples = languageCodeSamples.map(({ lang }) => lang);
const samplesLabels = languageCodeSamples.map(
({ label, lang }) => label || lang
);
const samplesSources = languageCodeSamples.map(({ source }) => source);
return {
...language,
sample: samples[0],
samples,
samplesSources,
samplesLabels,
};
}
return language;
});
}
export const mergeArraysbyLanguage = (arr1: any, arr2: any) => {
const mergedArray = unionBy(arr1, arr2, "language");
return mergedArray.map((item: any) => {
const matchingItems = [
find(arr1, ["language", item["language"]]),
find(arr2, ["language", item["language"]]),
];
return mergeWith({}, ...matchingItems, (objValue: any) => {
if (isArray(objValue)) {
return objValue;
}
return undefined;
});
});
};
export function getCodeSampleSourceFromLanguage(language: Language) {
if (
language &&
language.sample &&
language.samples &&
language.samplesSources
) {
const sampleIndex = language.samples.findIndex(
(smp) => smp === language.sample
);
return language.samplesSources[sampleIndex];
}
return "";
}
The CodeSnippets
directory should look like this:
src
└── theme
└── APIExplorer
└── CodeSnippets
├── index.tsx
├── code-snippets-types.ts
├── languages.json
└── languages.ts
5.2 Swizzle the CodeTabs Component
To ensure only programming languages with SDK examples are available in your documentation, swizzle the CodeTabs
component:
-
In the root of your documentation project, run:
npm run swizzle docusaurus-theme-openapi-docs ApiExplorer/CodeTabs -- --wrap --typescript
-
When prompted, select
YES: I know what I am doing!
:? Do you really want to swizzle this unsafe internal component?
› - Use arrow-keys. Return to submit.
NO: cancel and stay safe
❯ YES: I know what I am doing!
[Exit]This creates a wrapper in the
src/theme
folder:src
└── theme
└── APIExplorer
└── CodeTabs
└── index.tsx -
Replace the content in
CodeTabs/index.tsx
with:
import React, { cloneElement, ReactElement } from "react";
import {
sanitizeTabsChildren,
type TabProps,
useScrollPositionBlocker,
useTabs,
} from "@docusaurus/theme-common/internal";
import { TabItemProps } from "@docusaurus/theme-common/lib/utils/tabsUtils";
import useIsBrowser from "@docusaurus/useIsBrowser";
import { Language } from "@theme/ApiExplorer/CodeSnippets";
import clsx from "clsx";
export interface Props {
action: {
[key: string]: React.Dispatch<any>;
};
currentLanguage: Language;
languageSet: Language[];
includeVariant: boolean;
}
export interface CodeTabsProps extends Props, TabProps {
includeSample?: boolean;
}
function TabList({
action,
currentLanguage,
languageSet,
includeVariant,
includeSample,
className,
block,
selectedValue,
selectValue,
tabValues,
}: CodeTabsProps & ReturnType<typeof useTabs>) {
const tabRefs: (HTMLLIElement | null)[] = [];
const { blockElementScrollPositionUntilNextRender } =
useScrollPositionBlocker();
const handleTabChange = (
event:
| React.FocusEvent<HTMLLIElement>
| React.MouseEvent<HTMLLIElement>
| React.KeyboardEvent<HTMLLIElement>
) => {
const newTab = event.currentTarget;
const newTabIndex = tabRefs.indexOf(newTab);
const newTabValue = tabValues[newTabIndex]!.value;
if (newTabValue !== selectedValue) {
blockElementScrollPositionUntilNextRender(newTab);
selectValue(newTabValue);
}
};
const handleKeydown = (event: React.KeyboardEvent<HTMLLIElement>) => {
let focusElement: HTMLLIElement | null = null;
switch (event.key) {
case "Enter": {
handleTabChange(event);
break;
}
case "ArrowRight": {
const nextTab = tabRefs.indexOf(event.currentTarget) + 1;
focusElement = tabRefs[nextTab] ?? tabRefs[0]!;
break;
}
case "ArrowLeft": {
const prevTab = tabRefs.indexOf(event.currentTarget) - 1;
focusElement = tabRefs[prevTab] ?? tabRefs[tabRefs.length - 1]!;
break;
}
default:
break;
}
focusElement?.focus();
};
return (
<ul
role="tablist"
aria-orientation="horizontal"
className={clsx(
"tabs",
"openapi-tabs__code-list-container",
{
"tabs--block": block,
},
className
)}
>
{tabValues.map(({ value, label, attributes }) => (
<li
// TODO extract TabListItem
role="tab"
tabIndex={selectedValue === value ? 0 : -1}
aria-selected={selectedValue === value}
key={value}
ref={(tabControl) => tabRefs.push(tabControl)}
onKeyDown={handleKeydown}
onClick={handleTabChange}
{...attributes}
className={clsx(
"tabs__item",
"openapi-tabs__code-item",
attributes?.className as string,
{
active: selectedValue === value,
}
)}
>
<span>{label ?? value}</span>
</li>
))}
</ul>
);
}
function TabContent({
lazy,
children,
selectedValue,
}: CodeTabsProps & ReturnType<typeof useTabs>): React.JSX.Element | null {
const childTabs = (Array.isArray(children) ? children : [children]).filter(
Boolean
) as ReactElement<TabItemProps>[];
if (lazy) {
const selectedTabItem = childTabs.find(
(tabItem) => tabItem.props.value === selectedValue
);
if (!selectedTabItem) {
// fail-safe or fail-fast? not sure what's best here
return null;
}
return cloneElement(selectedTabItem, { className: "margin-top--md" });
}
return (
<div className="margin-top--md openapi-tabs__code-content">
{childTabs.map((tabItem, i) =>
cloneElement(tabItem, {
key: i,
hidden: tabItem.props.value !== selectedValue,
})
)}
</div>
);
}
function TabsComponent(props: CodeTabsProps & Props): React.JSX.Element {
const tabs = useTabs(props);
const { className } = props;
return (
<div
className={clsx("tabs-container openapi-tabs__code-container", className)}
>
<TabList {...props} {...tabs} />
<TabContent {...props} {...tabs} />
</div>
);
}
export default function CodeTabs(
props: CodeTabsProps & Props
): React.JSX.Element {
const isBrowser = useIsBrowser();
return (
<TabsComponent
// Remount tabs after hydration
// Temporary fix for https://github.com/facebook/docusaurus/issues/5653
key={String(isBrowser)}
{...props}
>
{sanitizeTabsChildren(props.children)}
</TabsComponent>
);
}
- Add a new
_CodeTabs.scss
file in theCodeTabs/
directory to define the component's styles:
:root {
--bash-background-color: transparent;
--bash-border-radius: none;
--code-tab-logo-width: 24px;
--code-tab-logo-height: 24px;
}
[data-theme="dark"] {
--bash-background-color: lightgrey;
--bash-border-radius: 20px;
}
.openapi-tabs__code-container {
margin-bottom: 1rem;
&:not(.openapi-tabs__code-container-inner) {
padding: 1rem;
background-color: var(--ifm-pre-background);
border-radius: var(--ifm-global-radius);
border: 1px solid var(--openapi-explorer-border-color);
box-shadow:
0 2px 3px hsla(222, 8%, 43%, 0.1),
0 8px 16px -10px hsla(222, 8%, 43%, 0.2);
transition: 300ms;
&:hover {
box-shadow:
0 0 0 2px rgba(38, 53, 61, 0.15),
0 2px 3px hsla(222, 8%, 43%, 0.15),
0 16px 16px -10px hsla(222, 8%, 43%, 0.2);
}
}
.openapi-tabs__code-item {
display: flex;
flex-direction: column-reverse;
flex: 0 0 80px;
align-items: center;
padding: 0.5rem 0 !important;
margin-top: 0 !important;
margin-right: 0.5rem;
border: 1px solid transparent;
transition: 300ms;
&:not(.active):hover {
border: 1px solid var(--openapi-code-tab-border-color);
}
&:hover {
background-color: transparent;
}
span {
padding-top: 0.5rem;
color: var(--ifm-font-color-secondary);
font-size: 10px;
text-transform: uppercase;
}
}
}
.openapi-tabs__code-list-container {
display: flex;
justify-content: flex-start;
padding: 0.25rem;
padding-bottom: 0.6rem;
}
.openapi-tabs__code-content {
margin-top: unset !important;
}
.openapi-explorer__code-block code {
max-height: 200px;
font-size: var(--openapi-explorer-font-size-code);
padding-top: var(--ifm-pre-padding);
}
body[class="ReactModal__Body--open"] {
.openapi-explorer__code-block code {
max-height: 600px;
}
}
.openapi-tabs__code-item--variant {
color: var(--ifm-color-secondary);
&.active {
border-color: var(--ifm-toc-border-color);
}
}
.openapi-tabs__code-item--variant > span {
padding-top: unset !important;
padding-left: 0.5rem !important;
padding-right: 0.5rem !important;
}
.openapi-tabs__code-item--sample {
color: var(--ifm-color-secondary);
&.active {
border-color: var(--ifm-toc-border-color);
}
}
.openapi-tabs__code-item--sample > span {
padding-top: unset !important;
padding-left: 0.5rem !important;
padding-right: 0.5rem !important;
}
.openapi-tabs__code-item--python {
color: var(--ifm-color-success);
&::after {
content: "";
width: var(--code-tab-logo-width);
height: var(--code-tab-logo-height);
background: url("https://raw.githubusercontent.com/devicons/devicon/master/icons/python/python-original.svg")
no-repeat;
margin-block: auto;
}
&.active {
box-shadow: 0 0 0 3px var(--openapi-code-tab-shadow-color-python);
border-color: var(--openapi-code-tab-border-color-python);
}
}
.openapi-tabs__code-item--go {
color: var(--ifm-color-info);
&::after {
content: "";
width: var(--code-tab-logo-width);
height: var(--code-tab-logo-height);
background: url("https://raw.githubusercontent.com/devicons/devicon/master/icons/go/go-original-wordmark.svg")
no-repeat;
margin-block: auto;
}
&.active {
box-shadow: 0 0 0 3px var(--openapi-code-tab-shadow-color-go);
border-color: var(--openapi-code-tab-border-color-go);
}
}
.openapi-tabs__code-item--javascript {
color: var(--ifm-color-warning);
&::after {
content: "";
width: var(--code-tab-logo-width);
height: var(--code-tab-logo-height);
background: url("https://raw.githubusercontent.com/devicons/devicon/master/icons/javascript/javascript-original.svg")
no-repeat;
margin-block: auto;
}
&.active {
box-shadow: 0 0 0 3px var(--openapi-code-tab-shadow-color-js);
border-color: var(--openapi-code-tab-border-color-js);
}
}
.openapi-tabs__code-item--bash {
color: var(--ifm-color-danger);
&::after {
content: "";
width: var(--code-tab-logo-width);
height: var(--code-tab-logo-height);
background: url("https://raw.githubusercontent.com/devicons/devicon/master/icons/bash/bash-plain.svg")
no-repeat;
margin-block: auto;
background-color: var(--bash-background-color);
border-radius: var(--bash-border-radius);
}
&.active {
box-shadow: 0 0 0 3px var(--openapi-code-tab-shadow-color-bash);
border-color: var(--ifm-color-danger);
}
}
.openapi-tabs__code-item--ruby {
color: var(--ifm-color-danger);
&::after {
content: "";
width: var(--code-tab-logo-width);
height: var(--code-tab-logo-height);
background: url("https://raw.githubusercontent.com/devicons/devicon/master/icons/ruby/ruby-plain.svg")
no-repeat;
margin-block: auto;
}
&.active {
box-shadow: 0 0 0 3px var(--openapi-code-tab-shadow-color-ruby);
border-color: var(--openapi-code-tab-border-color-ruby);
}
}
.openapi-tabs__code-item--csharp {
color: var(--ifm-color-gray-500);
&::after {
content: "";
width: var(--code-tab-logo-width);
height: var(--code-tab-logo-height);
background: url("https://raw.githubusercontent.com/devicons/devicon/master/icons/csharp/csharp-original.svg")
no-repeat;
margin-block: auto;
}
&.active {
box-shadow: 0 0 0 3px var(--openapi-code-tab-shadow-color-csharp);
border-color: var(--openapi-code-tab-border-color-csharp);
}
}
.openapi-tabs__code-item--nodejs {
color: var(--ifm-color-success);
&::after {
content: "";
width: var(--code-tab-logo-width);
height: var(--code-tab-logo-height);
background: url("https://raw.githubusercontent.com/devicons/devicon/master/icons/nodejs/nodejs-original.svg")
no-repeat;
margin-block: auto;
}
&.active {
box-shadow: 0 0 0 3px var(--opeanpi-code-tab-shadow-color-nodejs);
border-color: var(--openapi-code-tab-border-color-nodejs);
}
}
.openapi-tabs__code-item--php {
color: var(--ifm-color-gray-500);
&::after {
content: "";
width: var(--code-tab-logo-width);
height: var(--code-tab-logo-height);
background: url("https://raw.githubusercontent.com/devicons/devicon/master/icons/php/php-original.svg")
no-repeat;
margin-block: auto;
}
&.active {
box-shadow: 0 0 0 3px var(--openapi-code-tab-shadow-color-php);
border-color: var(--openapi-code-tab-border-color-php);
}
}
.openapi-tabs__code-item--java {
color: var(--ifm-color-warning);
&::after {
content: "";
width: var(--code-tab-logo-width);
height: var(--code-tab-logo-height);
background: url("https://raw.githubusercontent.com/devicons/devicon/master/icons/java/java-original.svg")
no-repeat;
margin-block: auto;
}
&.active {
box-shadow: 0 0 0 3px var(--openapi-code-tab-shadow-color-java);
border-color: var(--openapi-code-tab-border-color-java);
}
}
.openapi-tabs__code-item--powershell {
color: var(--ifm-color-info);
&::after {
content: "";
width: var(--code-tab-logo-width);
height: var(--code-tab-logo-height);
background: url("https://raw.githubusercontent.com/devicons/devicon/master/icons/windows8/windows8-original.svg")
no-repeat;
margin-block: auto;
}
&.active {
box-shadow: 0 0 0 3px var(--opeanpi-code-tab-shadow-color-powershell);
border-color: var(--openapi-code-tab-border-color-powershell);
}
}
@media only screen and (min-width: 768px) and (max-width: 996px) {
.openapi-tabs__code-list {
justify-content: space-around;
}
}
.ReactModal__Body--open {
overflow: hidden !important;
}
.openapi-modal--open {
background-color: rgba(0, 0, 0, 0.7) !important;
}
The CodeTabs
directory should look like this:
src
└── theme
└── APIExplorer
└── CodeTabs
├── index.tsx
└── _CodeTabs.scss
6. Test the SDKs Documentation
With the openapi.json
file updated to include the enhanced OpenAPI spec generated by liblab in Step 4, and after swizzling the CodeTabs
and CodeSnippets
components in Step 5, you're ready to review the final version of your documentation.
First, delete the existing endpoint pages and generate new ones based on the enhanced OpenAPI spec you added in Step 4. Run the following commands in your project's terminal:
npm run docusaurus clean-api-docs all
npm run docusaurus gen-api-docs all
Then, restart your documentation site by running:
npm run start
The project preview should open in your browser at http://localhost:3000/. Navigate to your documentation, select the PetStore tab, and open any endpoint page to view the code snippets from the generated SDKs now integrated into your documentation, as shown in the image below.
Conclusion
With liblab's enhanced OpenAPI capabilities, you now have a robust API reference that includes SDK code snippets and examples, making it easier for developers to use your API. As a result, your documentation now provides developers with quick, accurate references directly from the SDKs, reinforcing both usability and accessibility.