diff --git a/demo/examples/petstore.yaml b/demo/examples/petstore.yaml
index c27f0d903..2241e02b4 100644
--- a/demo/examples/petstore.yaml
+++ b/demo/examples/petstore.yaml
@@ -147,6 +147,7 @@ paths:
Console.WriteLine(response.getRawResponse());
}
- lang: PHP
+ label: Custom
source: |
$form = new \PetStore\Entities\Pet();
$form->setPetType("Dog");
diff --git a/packages/docusaurus-theme-openapi-docs/src/theme/ApiExplorer/CodeSnippets/code-snippets-types.ts b/packages/docusaurus-theme-openapi-docs/src/theme/ApiExplorer/CodeSnippets/code-snippets-types.ts
new file mode 100644
index 000000000..e46ed106d
--- /dev/null
+++ b/packages/docusaurus-theme-openapi-docs/src/theme/ApiExplorer/CodeSnippets/code-snippets-types.ts
@@ -0,0 +1,55 @@
+/* ============================================================================
+ * Copyright (c) Palo Alto Networks
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ * ========================================================================== */
+
+// 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;
+}
diff --git a/packages/docusaurus-theme-openapi-docs/src/theme/ApiExplorer/CodeSnippets/index.tsx b/packages/docusaurus-theme-openapi-docs/src/theme/ApiExplorer/CodeSnippets/index.tsx
index 9bd90e344..e0fe433d6 100644
--- a/packages/docusaurus-theme-openapi-docs/src/theme/ApiExplorer/CodeSnippets/index.tsx
+++ b/packages/docusaurus-theme-openapi-docs/src/theme/ApiExplorer/CodeSnippets/index.tsx
@@ -16,20 +16,14 @@ import CodeTabs from "@theme/ApiExplorer/CodeTabs";
import { useTypedSelector } from "@theme/ApiItem/hooks";
import merge from "lodash/merge";
-export interface Language {
- highlight: string;
- language: string;
- logoClass: string;
- variant: string;
- variants: string[];
- options: { [key: string]: boolean };
- source?: string;
-}
+import { CodeSample, Language } from "./code-snippets-types";
+import { mergeCodeSampleLanguage } from "./languages";
export const languageSet: Language[] = [
{
highlight: "bash",
language: "curl",
+ codeSampleLanguage: "Shell",
logoClass: "bash",
options: {
longFormat: false,
@@ -42,6 +36,7 @@ export const languageSet: Language[] = [
{
highlight: "python",
language: "python",
+ codeSampleLanguage: "Python",
logoClass: "python",
options: {
followRedirect: true,
@@ -53,6 +48,7 @@ export const languageSet: Language[] = [
{
highlight: "go",
language: "go",
+ codeSampleLanguage: "Go",
logoClass: "go",
options: {
followRedirect: true,
@@ -64,6 +60,7 @@ export const languageSet: Language[] = [
{
highlight: "javascript",
language: "nodejs",
+ codeSampleLanguage: "JavaScript",
logoClass: "nodejs",
options: {
ES6_enabled: true,
@@ -71,11 +68,12 @@ export const languageSet: Language[] = [
trimRequestBody: true,
},
variant: "axios",
- variants: ["axios", "native", "request", "unirest"],
+ variants: ["axios", "native"],
},
{
highlight: "ruby",
language: "ruby",
+ codeSampleLanguage: "Ruby",
logoClass: "ruby",
options: {
followRedirect: true,
@@ -87,6 +85,7 @@ export const languageSet: Language[] = [
{
highlight: "csharp",
language: "csharp",
+ codeSampleLanguage: "C#",
logoClass: "csharp",
options: {
followRedirect: true,
@@ -98,6 +97,7 @@ export const languageSet: Language[] = [
{
highlight: "php",
language: "php",
+ codeSampleLanguage: "PHP",
logoClass: "php",
options: {
followRedirect: true,
@@ -109,6 +109,7 @@ export const languageSet: Language[] = [
{
highlight: "java",
language: "java",
+ codeSampleLanguage: "Java",
logoClass: "java",
options: {
followRedirect: true,
@@ -120,6 +121,7 @@ export const languageSet: Language[] = [
{
highlight: "powershell",
language: "powershell",
+ codeSampleLanguage: "PowerShell",
logoClass: "powershell",
options: {
followRedirect: true,
@@ -132,10 +134,10 @@ export const languageSet: Language[] = [
export interface Props {
postman: sdk.Request;
- codeSamples: any; // TODO: Type this...
+ codeSamples: CodeSample[];
}
-function CodeTab({ children, hidden, className, onClick }: any): JSX.Element {
+function CodeTab({ children, hidden, className }: any): JSX.Element {
return (
{children}
@@ -165,7 +167,6 @@ function CodeSnippets({ postman, codeSamples }: Props) {
const langs = [
...((siteConfig?.themeConfig?.languageTabs as Language[] | undefined) ??
languageSet),
- ...codeSamples,
];
// Filter languageSet by user-defined langs
@@ -176,14 +177,18 @@ function CodeSnippets({ postman, codeSamples }: Props) {
});
// Merge user-defined langs into languageSet
- const mergedLangs = merge(filteredLanguageSet, langs);
+ const mergedLangs = mergeCodeSampleLanguage(
+ merge(filteredLanguageSet, langs),
+ codeSamples
+ );
// Read defaultLang from localStorage
const defaultLang: Language[] = mergedLangs.filter(
(lang) =>
lang.language === localStorage.getItem("docusaurus.tab.code-samples")
);
- const [selectedVariant, setSelectedVariant] = useState();
+ const [selectedVariant, setSelectedVariant] = useState
();
+ const [selectedSample, setSelectedSample] = useState();
const [language, setLanguage] = useState(() => {
// Return first index if only 1 user-defined language exists
if (mergedLangs.length === 1) {
@@ -192,9 +197,23 @@ function CodeSnippets({ postman, codeSamples }: Props) {
// Fall back to language in localStorage or first user-defined language
return defaultLang[0] ?? mergedLangs[0];
});
- const [codeText, setCodeText] = useState("");
+ const [codeText, setCodeText] = useState("");
+ const [codeSampleCodeText, setCodeSampleCodeText] = useState("");
useEffect(() => {
+ // initial active language is custom code sample
+ if (
+ language &&
+ language.sample &&
+ language.samples &&
+ language.samplesSources
+ ) {
+ const sampleIndex = language.samples.findIndex(
+ (smp) => smp === language.sample
+ );
+ setCodeSampleCodeText(language.samplesSources[sampleIndex]);
+ }
+
if (language && !!language.options) {
const postmanRequest = buildPostmanRequest(postman, {
queryParams,
@@ -219,8 +238,6 @@ function CodeSnippets({ postman, codeSamples }: Props) {
setCodeText(snippet);
}
);
- } else if (language && !!language.source) {
- setCodeText(language.source);
} else if (language && !language.options) {
const langSource = mergedLangs.filter(
(lang) => lang.language === language.language
@@ -271,8 +288,8 @@ function CodeSnippets({ postman, codeSamples }: Props) {
auth,
mergedLangs,
]);
-
- useEffect(() => {
+ // no dependencies was intentionlly set for this particular hook. it's safe as long as if conditions are set
+ useEffect(function onSelectedVariantUpdate() {
if (selectedVariant && selectedVariant !== language.variant) {
const postmanRequest = buildPostmanRequest(postman, {
queryParams,
@@ -300,6 +317,22 @@ function CodeSnippets({ postman, codeSamples }: Props) {
}
});
+ // no dependencies was intentionlly set for this particular hook. it's safe as long as if conditions are set
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ useEffect(function onSelectedSampleUpdate() {
+ if (
+ language.samples &&
+ language.samplesSources &&
+ selectedSample &&
+ selectedSample !== language.sample
+ ) {
+ const sampleIndex = language.samples.findIndex(
+ (smp) => smp === selectedSample
+ );
+ setCodeSampleCodeText(language.samplesSources[sampleIndex]);
+ }
+ });
+
if (language === undefined) {
return null;
}
@@ -324,6 +357,46 @@ function CodeSnippets({ postman, codeSamples }: Props) {
className: `openapi-tabs__code-item--${lang.logoClass}`,
}}
>
+ {lang.samples && (
+
+ {lang.samples.map((sample, index) => {
+ return (
+
+ {/* @ts-ignore */}
+
+ {codeSampleCodeText}
+
+
+ );
+ })}
+
+ )}
+
- {lang.variants.map((variant) => {
+ {lang.variants.map((variant, index) => {
return (
{
+ 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;
+ });
+}
diff --git a/packages/docusaurus-theme-openapi-docs/src/theme/ApiExplorer/CodeTabs/_CodeTabs.scss b/packages/docusaurus-theme-openapi-docs/src/theme/ApiExplorer/CodeTabs/_CodeTabs.scss
index 0b36f4b82..a734be72e 100644
--- a/packages/docusaurus-theme-openapi-docs/src/theme/ApiExplorer/CodeTabs/_CodeTabs.scss
+++ b/packages/docusaurus-theme-openapi-docs/src/theme/ApiExplorer/CodeTabs/_CodeTabs.scss
@@ -96,6 +96,20 @@ body[class="ReactModal__Body--open"] {
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);
diff --git a/packages/docusaurus-theme-openapi-docs/src/theme/ApiExplorer/CodeTabs/index.tsx b/packages/docusaurus-theme-openapi-docs/src/theme/ApiExplorer/CodeTabs/index.tsx
index b58e639b5..1777b68bb 100644
--- a/packages/docusaurus-theme-openapi-docs/src/theme/ApiExplorer/CodeTabs/index.tsx
+++ b/packages/docusaurus-theme-openapi-docs/src/theme/ApiExplorer/CodeTabs/index.tsx
@@ -26,16 +26,21 @@ export interface Props {
includeVariant: boolean;
}
+export interface TabListProps extends Props, TabProps {
+ includeSample?: boolean;
+}
+
function TabList({
action,
currentLanguage,
includeVariant,
+ includeSample,
className,
block,
selectedValue,
selectValue,
tabValues,
-}: Props & TabProps & ReturnType) {
+}: TabListProps & ReturnType) {
const tabRefs: (HTMLLIElement | null)[] = [];
const { blockElementScrollPositionUntilNextRender } =
useScrollPositionBlocker();
@@ -69,6 +74,10 @@ function TabList({
)[0];
action.setSelectedVariant(newLanguage.variant.toLowerCase());
}
+ if (currentLanguage && includeSample) {
+ newLanguage.sample = newTabValue;
+ action.setSelectedSample(newTabValue.toLowerCase());
+ }
action.setLanguage(newLanguage);
}
};
diff --git a/packages/docusaurus-theme-openapi-docs/src/theme/ApiExplorer/index.tsx b/packages/docusaurus-theme-openapi-docs/src/theme/ApiExplorer/index.tsx
index 747c58ee4..73766ae85 100644
--- a/packages/docusaurus-theme-openapi-docs/src/theme/ApiExplorer/index.tsx
+++ b/packages/docusaurus-theme-openapi-docs/src/theme/ApiExplorer/index.tsx
@@ -27,7 +27,7 @@ function ApiExplorer({
{item.method !== "event" && (
)}