diff --git a/demo/docusaurus.config.ts b/demo/docusaurus.config.ts index ed511aece..241523dfb 100644 --- a/demo/docusaurus.config.ts +++ b/demo/docusaurus.config.ts @@ -145,6 +145,9 @@ const config: Config = { "powershell", "json", "bash", + "dart", + "objectivec", + "r", ], }, languageTabs: [ @@ -194,6 +197,51 @@ const config: Config = { language: "powershell", logoClass: "powershell", }, + { + highlight: "dart", + language: "dart", + logoClass: "dart", + }, + { + highlight: "javascript", + language: "javascript", + logoClass: "javascript", + }, + { + highlight: "c", + language: "c", + logoClass: "c", + }, + { + highlight: "objective-c", + language: "objective-c", + logoClass: "objective-c", + }, + { + highlight: "ocaml", + language: "ocaml", + logoClass: "ocaml", + }, + { + highlight: "r", + language: "r", + logoClass: "r", + }, + { + highlight: "swift", + language: "swift", + logoClass: "swift", + }, + { + highlight: "kotlin", + language: "kotlin", + logoClass: "kotlin", + }, + { + highlight: "rust", + language: "rust", + logoClass: "rust", + }, ], algolia: { apiKey: "441074cace987cbf4640c039ebed303c", diff --git a/demo/src/css/custom.css b/demo/src/css/custom.css index 4ca833446..4955703cf 100644 --- a/demo/src/css/custom.css +++ b/demo/src/css/custom.css @@ -146,3 +146,19 @@ div[class^="announcementBar_"] { ); font-weight: bold; } + +.openapi-tabs__code-item--python { + color: var(--ifm-color-success); +} +.openapi-tabs__code-item--python::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-plain.svg") + no-repeat; + margin-block: auto; +} +.openapi-tabs__code-item--python.active { + box-shadow: 0 0 0 3px var(--openapi-code-tab-shadow-color-python); + border-color: var(--openapi-code-tab-border-color-python); +} 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 index e46ed106d..3f8f03a38 100644 --- 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 @@ -22,10 +22,12 @@ export type CodeSampleLanguage = | "JavaScript" | "Kotlin" | "Objective-C" + | "OCaml" | "Perl" | "PHP" | "PowerShell" | "Python" + | "R" | "Ruby" | "Rust" | "Scala" 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 aa1873453..b138fc8ca 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 @@ -27,120 +27,10 @@ import { getCodeSampleSourceFromLanguage, mergeArraysbyLanguage, mergeCodeSampleLanguage, + generateLanguageSet, } from "./languages"; -export const languageSet: Language[] = [ - { - highlight: "bash", - language: "curl", - codeSampleLanguage: "Shell", - logoClass: "bash", - options: { - longFormat: false, - followRedirect: true, - trimRequestBody: true, - }, - variant: "cURL", - variants: ["curl"], - }, - { - highlight: "python", - language: "python", - codeSampleLanguage: "Python", - logoClass: "python", - options: { - followRedirect: true, - trimRequestBody: true, - }, - variant: "requests", - variants: ["requests", "http.client"], - }, - { - highlight: "go", - language: "go", - codeSampleLanguage: "Go", - logoClass: "go", - options: { - followRedirect: true, - trimRequestBody: true, - }, - variant: "native", - variants: ["native"], - }, - { - highlight: "javascript", - language: "nodejs", - codeSampleLanguage: "JavaScript", - logoClass: "nodejs", - options: { - ES6_enabled: true, - followRedirect: true, - trimRequestBody: true, - }, - variant: "axios", - variants: ["axios", "native"], - }, - { - highlight: "ruby", - language: "ruby", - codeSampleLanguage: "Ruby", - logoClass: "ruby", - options: { - followRedirect: true, - trimRequestBody: true, - }, - variant: "Net::HTTP", - variants: ["net::http"], - }, - { - highlight: "csharp", - language: "csharp", - codeSampleLanguage: "C#", - logoClass: "csharp", - options: { - followRedirect: true, - trimRequestBody: true, - }, - variant: "RestSharp", - variants: ["restsharp", "httpclient"], - }, - { - highlight: "php", - language: "php", - codeSampleLanguage: "PHP", - logoClass: "php", - options: { - followRedirect: true, - trimRequestBody: true, - }, - variant: "cURL", - variants: ["curl", "guzzle", "pecl_http", "http_request2"], - }, - { - highlight: "java", - language: "java", - codeSampleLanguage: "Java", - logoClass: "java", - options: { - followRedirect: true, - trimRequestBody: true, - }, - variant: "OkHttp", - variants: ["okhttp", "unirest"], - }, - { - highlight: "powershell", - language: "powershell", - codeSampleLanguage: "PowerShell", - logoClass: "powershell", - options: { - followRedirect: true, - trimRequestBody: true, - }, - variant: "RestMethod", - variants: ["restmethod"], - }, -]; +export const languageSet: Language[] = generateLanguageSet(); export interface Props { postman: sdk.Request; @@ -339,6 +229,7 @@ function CodeSnippets({ postman, codeSamples }: Props) { return ( <> + {/* Outer language tabs */} {mergedLangs.map((lang) => { @@ -359,6 +251,7 @@ function CodeSnippets({ postman, codeSamples }: Props) { className: `openapi-tabs__code-item--${lang.logoClass}`, }} > + {/* Inner x-codeSamples tabs */} {lang.samples && ( )} + {/* Inner generated code snippets */} { + const variants: any = []; + language.variants.forEach((variant: any) => { + variants.push(variant.key); + }); + languageSet.push({ + highlight: language.syntax_mode, + language: language.key, + codeSampleLanguage: language.label, + logoClass: language.key, + options: { + longFormat: false, + followRedirect: true, + trimRequestBody: true, + }, + variant: variants[0], + variants: variants, + }); + }); + return languageSet; +} 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 a734be72e..4d6301f41 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 @@ -146,6 +146,24 @@ body[class="ReactModal__Body--open"] { } } +.openapi-tabs__code-item--dart { + 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/dart/dart-original.svg") + no-repeat; + margin-block: auto; + } + + &.active { + box-shadow: 0 0 0 3px var(--openapi-code-tab-shadow-color-dart); + border-color: var(--openapi-code-tab-border-color-dart); + } +} + .openapi-tabs__code-item--javascript { color: var(--ifm-color-warning); @@ -164,7 +182,7 @@ body[class="ReactModal__Body--open"] { } } -.openapi-tabs__code-item--bash { +.openapi-tabs__code-item--curl { color: var(--ifm-color-danger); &::after { @@ -179,7 +197,7 @@ body[class="ReactModal__Body--open"] { } &.active { - box-shadow: 0 0 0 3px var(--openapi-code-tab-shadow-color-bash); + box-shadow: 0 0 0 3px var(--openapi-code-tab-shadow-color-curl); border-color: var(--ifm-color-danger); } } @@ -220,6 +238,96 @@ body[class="ReactModal__Body--open"] { } } +.openapi-tabs__code-item--r { + 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/r/r-original.svg") + no-repeat; + margin-block: auto; + } + + &.active { + box-shadow: 0 0 0 3px var(--openapi-code-tab-shadow-color-r); + border-color: var(--openapi-code-tab-border-color-r); + } +} + +.openapi-tabs__code-item--swift { + 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/swift/swift-original.svg") + no-repeat; + margin-block: auto; + } + + &.active { + box-shadow: 0 0 0 3px var(--openapi-code-tab-shadow-color-swift); + border-color: var(--openapi-code-tab-border-color-swift); + } +} + +.openapi-tabs__code-item--c { + 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/c/c-original.svg") + no-repeat; + margin-block: auto; + } + + &.active { + box-shadow: 0 0 0 3px var(--openapi-code-tab-shadow-color-c); + border-color: var(--openapi-code-tab-border-color-c); + } +} + +.openapi-tabs__code-item--objective-c { + 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/objectivec/objectivec-plain.svg") + no-repeat; + margin-block: auto; + } + + &.active { + box-shadow: 0 0 0 3px var(--openapi-code-tab-shadow-color-objective-c); + border-color: var(--openapi-code-tab-border-color-objective-c); + } +} + +.openapi-tabs__code-item--ocaml { + 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/ocaml/ocaml-original.svg") + no-repeat; + margin-block: auto; + } + + &.active { + box-shadow: 0 0 0 3px var(--openapi-code-tab-shadow-color-objective-ocaml); + border-color: var(--openapi-code-tab-border-color-objective-ocaml); + } +} + .openapi-tabs__code-item--nodejs { color: var(--ifm-color-success); @@ -256,6 +364,42 @@ body[class="ReactModal__Body--open"] { } } +.openapi-tabs__code-item--kotlin { + 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/kotlin/kotlin-original.svg") + no-repeat; + margin-block: auto; + } + + &.active { + box-shadow: 0 0 0 3px var(--openapi-code-tab-shadow-color-kotlin); + border-color: var(--openapi-code-tab-border-color-kotlin); + } +} + +.openapi-tabs__code-item--rust { + 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/rust/rust-original.svg") + no-repeat; + margin-block: auto; + } + + &.active { + box-shadow: 0 0 0 3px var(--openapi-code-tab-shadow-color-rust); + border-color: var(--openapi-code-tab-border-color-rust); + } +} + .openapi-tabs__code-item--java { color: var(--ifm-color-warning); 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 15d5c067e..81311a38d 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 @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. * ========================================================================== */ -import React, { cloneElement, ReactElement } from "react"; +import React, { cloneElement, ReactElement, useEffect, useRef } from "react"; import { sanitizeTabsChildren, @@ -43,10 +43,23 @@ function TabList({ selectValue, tabValues, }: CodeTabsProps & ReturnType) { - const tabRefs: (HTMLLIElement | null)[] = []; + const tabRefs = useRef<(HTMLLIElement | null)[]>([]); const { blockElementScrollPositionUntilNextRender } = useScrollPositionBlocker(); + useEffect(() => { + const activeTab = tabRefs.current.find( + (tab) => tab?.getAttribute("aria-selected") === "true" + ); + if (activeTab) { + activeTab.scrollIntoView({ + behavior: "auto", + block: "center", + inline: "start", + }); + } + }, []); + const handleTabChange = ( event: | React.FocusEvent @@ -54,7 +67,7 @@ function TabList({ | React.KeyboardEvent ) => { const newTab = event.currentTarget; - const newTabIndex = tabRefs.indexOf(newTab); + const newTabIndex = tabRefs.current.indexOf(newTab); const newTabValue = tabValues[newTabIndex]!.value; if (newTabValue !== selectedValue) { @@ -80,7 +93,7 @@ function TabList({ newLanguage = languageSet.filter( (lang: Language) => lang.language === newTabValue )[0]; - action.setSelectedVariant(newLanguage.variant.toLowerCase()); + action.setSelectedVariant(newLanguage.variants[0].toLowerCase()); action.setSelectedSample(newLanguage.sample); } action.setLanguage(newLanguage); @@ -96,13 +109,15 @@ function TabList({ break; } case "ArrowRight": { - const nextTab = tabRefs.indexOf(event.currentTarget) + 1; - focusElement = tabRefs[nextTab] ?? tabRefs[0]!; + const nextTab = tabRefs.current.indexOf(event.currentTarget) + 1; + focusElement = tabRefs.current[nextTab] ?? tabRefs.current[0]!; break; } case "ArrowLeft": { - const prevTab = tabRefs.indexOf(event.currentTarget) - 1; - focusElement = tabRefs[prevTab] ?? tabRefs[tabRefs.length - 1]!; + const prevTab = tabRefs.current.indexOf(event.currentTarget) - 1; + focusElement = + tabRefs.current[prevTab] ?? + tabRefs.current[tabRefs.current.length - 1]!; break; } default: @@ -132,7 +147,7 @@ function TabList({ tabIndex={selectedValue === value ? 0 : -1} aria-selected={selectedValue === value} key={value} - ref={(tabControl) => tabRefs.push(tabControl)} + ref={(tabControl) => tabRefs.current.push(tabControl)} onKeyDown={handleKeydown} onClick={handleTabChange} {...attributes}