Repository: enzonotario/vitepress-openapi (ts)

Issue:
Request to add data-title attribute to code block group labels
### What would you like?

There is another package that allows for adding icons to code groups. This is commonly found on other VitePress sites when displaying commands or programming languages. It appears that the code group relies on there being a data-title attribute for it to display icons.

The code group icon package is called: vitepress-plugin-group-icons
https://github.com/yuyinws/vitepress-plugin-group-icons

You can see it in use here on the main VitePress site: https://vitepress.dev/guide/getting-started in the installation section.

I should note that the package seems to look for file extensions when displaying language icons but I used custom icons in my implementation so that it could work with the vitepress-openapi implementation.

![Image](https://github.com/user-attachments/assets/4733a151-fd72-4060-9250-dba894e7b5e6)

### Why is this needed?

It may be common for people to utilize code group icons elsewhere in their documentation and it would be helpful for the openapi generated pages to be able to display icons in those code groups as well.

### How could it be implemented?

I believe this could be achieved by changing the OACodeSamples component and adding sample.lang in a data-title attribute added to the label. I don't believe this would impact functionality for people not using code group icons but would allow those that are, compatibility when combining it with custom icons.

*I should note that there appears to be some oddities that deal with the capitalization, or lack thereof for the value in the data-title attribute, but I plan to bring that up to the code group icon package maintainer when I can narrow down the issue.

![Image](https://github.com/user-attachments/assets/56425bf0-ec51-4f65-8b86-25e577888452)

### Other information

_No response_

Pull request description:
feat(CodeSamples): add support for `vitepress-plugin-group-icons` to show icons in Code Samples
# Description

Add support for `vitepress-plugin-group-icons` to show icons in Code Samples.

## Related issues/external references

Closes #198 

## Types of changes

- Style

Patch:
diff --git a/docs/.vitepress/theme/components/BrowserWindow.vue b/docs/.vitepress/theme/components/BrowserWindow.vue
new file mode 100644
index 0000000..a04f335
--- /dev/null
+++ b/docs/.vitepress/theme/components/BrowserWindow.vue
@@ -0,0 +1,40 @@
+<script setup lang="ts">
+import { cn } from '../../../../src/lib/utils'
+
+const props = defineProps({
+  showBrowserDots: {
+    type: Boolean,
+    default: true,
+  },
+  className: {
+    type: String,
+    default: '',
+  },
+  shadow: {
+    type: Boolean,
+    default: true,
+  },
+})
+</script>
+
+<template>
+  <div
+    class="rounded border overflow-hidden"
+    :class="cn({
+      'shadow-lg': props.shadow,
+    }, props.className)"
+  >
+    <div
+      v-if="props.showBrowserDots"
+      class="flex items-center gap-2 px-4 py-2 bg-gray-100 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700"
+    >
+      <span class="w-3 h-3 bg-red-500 rounded-full" />
+      <span class="w-3 h-3 bg-yellow-500 rounded-full" />
+      <span class="w-3 h-3 bg-green-500 rounded-full" />
+    </div>
+
+    <div class="relative w-full h-full overflow-x-hidden">
+      <slot />
+    </div>
+  </div>
+</template>
diff --git a/docs/.vitepress/theme/components/ExampleBlock.vue b/docs/.vitepress/theme/components/ExampleBlock.vue
index 2bba207..4abadac 100644
--- a/docs/.vitepress/theme/components/ExampleBlock.vue
+++ b/docs/.vitepress/theme/components/ExampleBlock.vue
@@ -1,13 +1,44 @@
 <script setup lang="ts">
+import { cn } from '../../../../src/lib/utils'
+import BrowserWindow from './BrowserWindow.vue'
+
+const props = defineProps({
+  browserWindowClass: {
+    type: String,
+    default: '',
+  },
+})
 </script>
 
 <template>
-  <div class="flex flex-col">
-    <slot name="code" />
+  <div class="ExampleBlock flex flex-col gap-4">
+    <div class="flex flex-col gap-1">
+      <h3 class="!my-0">
+        Example
+      </h3>
+
+      <slot name="code" />
+    </div>
 
-    <details class="rounded border" style="padding: 24px; margin: 16px 0;" open>
-      <summary>Example</summary>
-      <slot name="example" />
-    </details>
+    <div class="flex flex-col gap-1">
+      <h3 class="!my-0">
+        Preview
+      </h3>
+
+      <BrowserWindow :class="cn('', props.browserWindowClass)">
+        <div class="p-4 lg:p-8">
+          <slot name="example" />
+        </div>
+      </BrowserWindow>
+    </div>
   </div>
 </template>
+
+<style>
+.ExampleBlock .vp-adaptive-theme {
+  height: 100%;
+  max-height: 60vh;
+  overflow-y: auto;
+  margin: 0 !important;
+}
+</style>
diff --git a/docs/.vitepress/theme/components/sandbox/SandboxIframe.vue b/docs/.vitepress/theme/components/sandbox/SandboxIframe.vue
index 7edab4f..d430da2 100644
--- a/docs/.vitepress/theme/components/sandbox/SandboxIframe.vue
+++ b/docs/.vitepress/theme/components/sandbox/SandboxIframe.vue
@@ -3,6 +3,7 @@ import { compressToURL } from '@amoutonbrady/lz-string'
 import { deepUnref } from '../../../../../src/lib/deepUnref'
 import { cn } from '../../../../../src/lib/utils'
 import { initSandboxData } from '../../sandboxData'
+import BrowserWindow from '../BrowserWindow.vue'
 
 const props = defineProps({
   themeConfig: {
@@ -23,10 +24,6 @@ const props = defineProps({
     type: Number,
     default: 1,
   },
-  browserWindow: {
-    type: Boolean,
-    default: true,
-  },
 })
 
 const sandboxData = initSandboxData({
@@ -57,21 +54,7 @@ const url = `${baseUrl}?themeConfig=${themeConfigCompressed}&sandboxData=${sandb
 </script>
 
 <template>
-  <div
-    class="rounded border overflow-hidden"
-    :class="{
-      'shadow-lg': props.browserWindow,
-    }"
-  >
-    <div
-      v-if="props.browserWindow"
-      class="flex items-center gap-2 px-4 py-2 bg-gray-100 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700"
-    >
-      <span class="w-3 h-3 bg-red-500 rounded-full" />
-      <span class="w-3 h-3 bg-yellow-500 rounded-full" />
-      <span class="w-3 h-3 bg-green-500 rounded-full" />
-    </div>
-
+  <BrowserWindow>
     <div class="relative w-full h-full overflow-x-hidden">
       <iframe
         :class="cn([
@@ -102,9 +85,5 @@ const url = `${baseUrl}?themeConfig=${themeConfigCompressed}&sandboxData=${sandb
         </a>
       </div>
     </div>
-  </div>
+  </BrowserWindow>
 </template>
-
-<style scoped>
-/* Estilos adicionales si los necesitas */
-</style>
diff --git a/docs/customizations/code-samples.md b/docs/customizations/code-samples.md
index c16476b..41fac72 100644
--- a/docs/customizations/code-samples.md
+++ b/docs/customizations/code-samples.md
@@ -14,15 +14,14 @@ You can customize:
 - Available languages for selection (`availableLangs`)
 - How code is generated for each language (`generator`)
 
-
-<ExampleBlock>
-
-<template #code>
-
 ## Custom Languages
 
 For example, you can add [Bru Markup Language](https://docs.usebruno.com/bru-lang/overview) to the list of languages to show and available languages to select from and a generator to convert the request object to Bru code.
 
+<ExampleBlock browser-window-class="h-[70vh] max-h-[700px]">
+
+<template #code>
+
 ```markdown
 ---
 aside: false
@@ -42,3 +41,47 @@ title: vitepress-openapi
 </template>
 
 </ExampleBlock>
+
+## Using Icons with `vitepress-plugin-group-icons`
+
+You can enhance your code samples by displaying custom icons for each programming language using the [`vitepress-plugin-group-icons`](https://github.com/yuyinws/vitepress-plugin-group-icons) plugin.
+
+`vitepress-plugin-group-icons` is a markdown-it plugin that processes your Code Groups and only loads the icons you actually use to display them. On the other hand, `vitepress-openapi` is a set of Vue components for visualizing your OpenAPI Specification. Therefore, unless you also use the icons in your Markdown code, `vitepress-plugin-group-icons` will not load the icons that are used in the Vue components of `vitepress-openapi`.
+
+In this case, you should instruct `vitepress-plugin-group-icons` to load certain icons by default, regardless of whether they are used in Markdown files or not.
+
+You can do this by configuring the plugin as follows:
+
+```ts {2,7-22} [.vitepress/config.ts]
+import { defineConfigWithTheme } from 'vitepress'
+import { groupIconVitePlugin } from 'vitepress-plugin-group-icons'
+
+export default defineConfigWithTheme({
+  // Your VitePress configuration...
+  
+  vite: {
+    plugins: [
+      groupIconVitePlugin({
+        customIcon: {
+          curl: 'simple-icons:curl', // Custom icon for curl.
+        },
+        defaultLabels: [ // Preload icons for specific labels.
+          'curl',
+          '.ts',
+          '.js',
+          '.py',
+          '.php',
+        ],
+      }),
+    ],
+  },
+})
+```
+
+<div class="flex justify-center">
+<div class="tip custom-block w-full max-w-xl p-4 text-center">
+
+[Live Example](https://vitepress-openapi-vitepress-plugin-group-icons.vercel.app/) | [Source Code](https://github.com/enzonotario/vitepress-openapi-vitepress-plugin-group-icons)
+
+</div>
+</div>
diff --git a/docs/customizations/custom-server.md b/docs/customizations/custom-server.md
index 4e12607..5f918de 100644
--- a/docs/customizations/custom-server.md
+++ b/docs/customizations/custom-server.md
@@ -48,13 +48,11 @@ useTheme({
 
 </ScopeConfigurationTabs>
 
-<ExampleBlock>
-
-<template #code>
+---
 
-## Example
+<ExampleBlock browser-window-class="h-[70vh] max-h-[700px]">
 
-The following example is generated from this code:
+<template #code>
 
 ```markdown
 ---
diff --git a/docs/customizations/custom-slots.md b/docs/customizations/custom-slots.md
index 0bceda0..0a10a7e 100644
--- a/docs/customizations/custom-slots.md
+++ b/docs/customizations/custom-slots.md
@@ -7,14 +7,14 @@ outline: false
 
 The `OAOperation` component provides several slots for customizing the operation layout.
 
-<ExampleBlock>
-
-<template #code>
-
 ## Description
 
 The `description` slot allows you to customize the operation description.
 
+<ExampleBlock>
+
+<template #code>
+
 ```markdown
 ---
 aside: false
diff --git a/docs/customizations/operation-badges.md b/docs/customizations/operation-badges.md
index 72e4e97..b722245 100644
--- a/docs/customizations/operation-badges.md
+++ b/docs/customizations/operation-badges.md
@@ -12,7 +12,7 @@ Each operation can have different badges that indicate its state, for example if
 
 By default, only the `deprecated` badge is shown, as appropriate. You can customize the operation badges using the `useTheme({ operation: { badges: string[] })` function. **The order in which you set the badges is the order in which they will be displayed.**
 
-<ExampleBlock>
+<ExampleBlock browser-window-class="h-[70vh] max-h-[700px]">
 
 <template #code>
 
@@ -36,6 +36,8 @@ title: vitepress-openapi
 
 </ExampleBlock>
 
+<span style="width:100%;height:16px;display:block"></span>
+
 # Custom Prefix
 
 You can also customize the prefix of the badges by setting the `operation.badgePrefix.{badgeName}` key in the i18n messages. For example, in your `.vitepress/theme/index.ts`, before calling `theme.enhanceApp({ app })`, you can set the following:
diff --git a/docs/customizations/operation-tags-slot.md b/docs/customizations/operation-tags-slot.md
index 2a993c1..eb09c6f 100644
--- a/docs/customizations/operation-tags-slot.md
+++ b/docs/customizations/operation-tags-slot.md
@@ -7,7 +7,7 @@ outline: false
 
 You can include the tags of an operation using `useTheme({ operation: { slots: [ ...DEFAULT_OPERATION_SLOTS, 'tags' ] })`.
 
-<ExampleBlock>
+<ExampleBlock browser-window-class="h-[70vh] max-h-[700px]">
 
 <template #code>
 
diff --git a/docs/public/openapi-parameters.json b/docs/public/openapi-parameters.json
index aa554fd..3a530ec 100644
--- a/docs/public/openapi-parameters.json
+++ b/docs/public/openapi-parameters.json
@@ -11,6 +11,118 @@
     }
   ],
   "paths": {
+    "/users": {
+      "get": {
+        "summary": "List users",
+        "description": "Returns a list of users with optional filtering.",
+        "parameters": [
+          {
+            "name": "name",
+            "in": "query",
+            "required": false,
+            "description": "Filter users by name",
+            "schema": {
+              "type": "string",
+              "example": "John"
+            }
+          },
+          {
+            "name": "age",
+            "in": "query",
+            "required": false,
+            "description": "Filter users by age",
+            "schema": {
+              "type": "integer",
+              "example": 25,
+              "minimum": 0,
+              "maximum": 120,
+              "multipleOf": 1
+            }
+          },
+          {
+            "name": "limit",
+            "in": "query",
+            "required": false,
+            "description": "Maximum number of users to return",
+            "schema": {
+              "type": "integer",
+              "example": 10,
+              "minimum": 1,
+              "maximum": 100,
+              "default": 20
+            }
+          },
+          {
+            "name": "offset",
+            "in": "query",
+            "required": false,
+            "description": "Number of users to skip for pagination",
+            "schema": {
+              "type": "integer",
+              "example": 0,
+              "minimum": 0,
+              "default": 0
+            }
+          },
+          {
+            "name": "X-API-Key",
+            "in": "header",
+            "required": true,
+            "description": "API key for authentication",
+            "schema": {
+              "type": "string",
+              "example": "api-key-123"
+            }
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "Successful response",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "type": "object",
+                  "properties": {
+                    "users": {
+                      "type": "array",
+                      "items": {
+                        "type": "object",
+                        "properties": {
+                          "userId": {
+                            "type": "string"
+                          },
+                          "name": {
+                            "type": "string"
+                          },
+                          "age": {
+                            "type": "integer"
+                          }
+                        }
+                      }
+                    },
+                    "total": {
+                      "type": "integer"
+                    },
+                    "limit": {
+                      "type": "integer"
+                    },
+                    "offset": {
+                      "type": "integer"
+                    }
+                  }
+                }
+              }
+            }
+          },
+          "400": {
+            "description": "Bad request"
+          },
+          "401": {
+            "description": "Unauthorized - Invalid API key"
+          }
+        }
+      }
+    },
     "/users/{userId}": {
       "get": {
         "summary": "Get user information by ID",
diff --git a/docs/sidebar/sidebar-items.md b/docs/sidebar/sidebar-items.md
index 27703cb..3b6c05f 100644
--- a/docs/sidebar/sidebar-items.md
+++ b/docs/sidebar/sidebar-items.md
@@ -50,7 +50,7 @@ module.exports = {
 
 </div>
 
-<SandboxIframe :sandbox-data="{sandboxView: 'preview', sidebarItemsType: 'default'}" non-interactive iframe-class="w-[1200px]" class="h-[70 sticky top-[calc(var(--vp-nav-height+16px))]vh] max-h-[700px]" />
+<SandboxIframe :sandbox-data="{sandboxView: 'preview', sidebarItemsType: 'default'}" non-interactive iframe-class="w-[1200px]" class="h-[70vh] sticky top-[calc(var(--vp-nav-height+16px))]vh] max-h-[700px]" />
 
 </div>
 
diff --git a/src/components/Feature/OAOperationContent.vue b/src/components/Feature/OAOperationContent.vue
index b6fa1b9..bf9120a 100644
--- a/src/components/Feature/OAOperationContent.vue
+++ b/src/components/Feature/OAOperationContent.vue
@@ -20,10 +20,6 @@ const props = defineProps({
     type: String,
     required: true,
   },
-  spec: {
-    type: Object,
-    required: false,
-  },
   openapi: {
     type: Object,
     required: true,
diff --git a/src/components/Feature/OASpecContent.vue b/src/components/Feature/OASpecContent.vue
index 814ae55..0121d49 100644
--- a/src/components/Feature/OASpecContent.vue
+++ b/src/components/Feature/OASpecContent.vue
@@ -10,11 +10,6 @@ import OAInfoContent from './OAInfoContent.vue'
 import OAServersContent from './OAServersContent.vue'
 
 const props = defineProps({
-  spec: {
-    type: Object,
-    required: false,
-    default: null,
-  },
   hideInfo: {
     type: Boolean,
     default: false,
diff --git a/src/components/Sample/OACodeSamples.vue b/src/components/Sample/OACodeSamples.vue
index 6eb4b6e..db9efd9 100644
--- a/src/components/Sample/OACodeSamples.vue
+++ b/src/components/Sample/OACodeSamples.vue
@@ -80,7 +80,10 @@ watch(() => props.request, async () => {
             :checked="sample.lang === defaultLang"
             @change="activeSample = sample.lang"
           >
-          <label :for="`tab-${props.operationId}-${sample.lang}`">{{ sample.label || sample.lang }}</label>
+          <label
+            :for="`tab-${props.operationId}-${sample.lang}`"
+            :data-title="sample.icon"
+          >{{ sample.label || sample.lang }}</label>
         </template>
       </div>
 
diff --git a/src/composables/useTheme.ts b/src/composables/useTheme.ts
index 84f3b70..ec76bf3 100644
--- a/src/composables/useTheme.ts
+++ b/src/composables/useTheme.ts
@@ -143,6 +143,7 @@ interface LanguageConfig {
   lang: string
   label: string
   highlighter: string
+  icon?: string
 }
 
 export type PartialUseThemeConfig = Partial<UnwrapNestedRefs<UseThemeConfig>>
@@ -172,21 +173,25 @@ export const availableLanguages: LanguageConfig[] = [
     lang: 'curl',
     label: 'cURL',
     highlighter: 'bash',
+    icon: 'curl',
   },
   {
     lang: 'javascript',
     label: 'JavaScript',
     highlighter: 'javascript',
+    icon: '.js',
   },
   {
     lang: 'php',
     label: 'PHP',
     highlighter: 'php',
+    icon: '.php',
   },
   {
     lang: 'python',
     label: 'Python',
     highlighter: 'python',
+    icon: '.py',
   },
 ]
 
@@ -786,7 +791,10 @@ export function useTheme(initialConfig: PartialUseThemeConfig = {}) {
     const uniqueLanguages = [...new Set(languages.map(({ lang }) => lang))]
 
     themeConfig.codeSamples.availableLanguages = uniqueLanguages.map((lang) => {
-      const language = availableLanguages.find(l => l.lang === lang)
+      const language
+        = languages.find(l => l.lang === lang)
+          ?? availableLanguages.find(l => l.lang === lang)
+
       return language || { lang, label: lang, highlighter: 'plaintext' }
     })
   }


Test patch:
diff --git a/test/composables/useTheme.test.ts b/test/composables/useTheme.test.ts
index ad995c0..ba788ef 100644
--- a/test/composables/useTheme.test.ts
+++ b/test/composables/useTheme.test.ts
@@ -456,3 +456,155 @@ describe('server configuration', () => {
     expect(themeConfig.getServerConfig().getServers).toBeTypeOf('function')
   })
 })
+
+describe('codeSamples configuration', () => {
+  const themeConfig = useTheme()
+
+  beforeEach(() => {
+    themeConfig.reset()
+  })
+
+  it('returns the default code samples languages', () => {
+    const result = themeConfig.getCodeSamplesLangs()
+    expect(result).toEqual(['curl', 'javascript', 'php', 'python'])
+  })
+
+  it('returns the default code samples default language', () => {
+    const result = themeConfig.getCodeSamplesDefaultLang()
+    expect(result).toBe('curl')
+  })
+
+  it('returns the default code samples available languages', () => {
+    const result = themeConfig.getCodeSamplesAvailableLanguages()
+    expect(result).toEqual([
+      {
+        lang: 'curl',
+        label: 'cURL',
+        highlighter: 'bash',
+        icon: 'curl',
+      },
+      {
+        lang: 'javascript',
+        label: 'JavaScript',
+        highlighter: 'javascript',
+        icon: '.js',
+      },
+      {
+        lang: 'php',
+        label: 'PHP',
+        highlighter: 'php',
+        icon: '.php',
+      },
+      {
+        lang: 'python',
+        label: 'Python',
+        highlighter: 'python',
+        icon: '.py',
+      },
+    ])
+  })
+
+  it('returns the default code samples generator', () => {
+    const result = themeConfig.getCodeSamplesGenerator()
+    expect(result).toBeTypeOf('function')
+  })
+
+  it('returns the default code samples default headers', () => {
+    const result = themeConfig.getCodeSamplesDefaultHeaders()
+    expect(result).toEqual({})
+  })
+
+  it('sets and gets the code samples config', () => {
+    const customGenerator = (lang: string, _request: any) => Promise.resolve(`Custom code for ${lang}`)
+    const customHeaders = { 'X-Custom-Header': 'value' }
+
+    themeConfig.setCodeSamplesConfig({
+      langs: ['javascript', 'python', 'go'],
+      defaultLang: 'python',
+      availableLanguages: [
+        {
+          lang: 'go',
+          label: 'Go',
+          highlighter: 'go',
+          icon: '.go',
+        },
+      ],
+      generator: customGenerator,
+      defaultHeaders: customHeaders,
+    })
+
+    expect(themeConfig.getCodeSamplesLangs()).toEqual(['javascript', 'python', 'go'])
+    expect(themeConfig.getCodeSamplesDefaultLang()).toBe('python')
+
+    const availableLanguages = themeConfig.getCodeSamplesAvailableLanguages()
+    expect(availableLanguages).toEqual([
+      {
+        lang: 'go',
+        label: 'Go',
+        highlighter: 'go',
+        icon: '.go',
+      },
+    ])
+
+    expect(themeConfig.getCodeSamplesGenerator()).toBe(customGenerator)
+    expect(themeConfig.getCodeSamplesDefaultHeaders()).toEqual(customHeaders)
+  })
+
+  it('handles duplicate languages in langs array', () => {
+    themeConfig.setCodeSamplesConfig({
+      langs: ['javascript', 'javascript', 'python'],
+    })
+
+    expect(themeConfig.getCodeSamplesLangs()).toEqual(['javascript', 'python'])
+  })
+
+  it('falls back to first available language when default language is not in available languages', () => {
+    themeConfig.setCodeSamplesConfig({
+      langs: ['javascript', 'python'],
+      defaultLang: 'ruby', // Not in the available languages
+    })
+
+    expect(themeConfig.getCodeSamplesDefaultLang()).toBe('javascript')
+  })
+
+  it('can setup icons', () => {
+    themeConfig.setCodeSamplesConfig({
+      availableLanguages: [
+        ...themeConfig.getCodeSamplesAvailableLanguages().map((lang) => {
+          if (lang.lang === 'javascript') {
+            return { ...lang, icon: 'custom-js-icon' }
+          }
+          return lang
+        }),
+      ],
+    })
+
+    const result = themeConfig.getCodeSamplesAvailableLanguages()
+    expect(result).toEqual([
+      {
+        lang: 'curl',
+        label: 'cURL',
+        highlighter: 'bash',
+        icon: 'curl',
+      },
+      {
+        lang: 'javascript',
+        label: 'JavaScript',
+        highlighter: 'javascript',
+        icon: 'custom-js-icon',
+      },
+      {
+        lang: 'php',
+        label: 'PHP',
+        highlighter: 'php',
+        icon: '.php',
+      },
+      {
+        lang: 'python',
+        label: 'Python',
+        highlighter: 'python',
+        icon: '.py',
+      },
+    ])
+  })
+})
diff --git a/test/lib/processOpenAPI/__snapshots__/processOpenAPI.test.ts.snap b/test/lib/processOpenAPI/__snapshots__/processOpenAPI.test.ts.snap
index 35075eb..f7fc02e 100644
--- a/test/lib/processOpenAPI/__snapshots__/processOpenAPI.test.ts.snap
+++ b/test/lib/processOpenAPI/__snapshots__/processOpenAPI.test.ts.snap
@@ -454,18 +454,21 @@ All descriptions *can* contain ~~tons of text~~ **Markdown**. [If GitHub support
         "codeSamples": [
           {
             "highlighter": "bash",
+            "icon": "curl",
             "label": "cURL",
             "lang": "curl",
             "source": "curl 'https://stoplight.io/mocks/enzonotario/argentine-rock/122547792/api/v1/artists?limit=10&offset=1'",
           },
           {
             "highlighter": "javascript",
+            "icon": ".js",
             "label": "JavaScript",
             "lang": "javascript",
             "source": "fetch('https://stoplight.io/mocks/enzonotario/argentine-rock/122547792/api/v1/artists?limit=10&offset=1')",
           },
           {
             "highlighter": "php",
+            "icon": ".php",
             "label": "PHP",
             "lang": "php",
             "source": "$ch = curl_init("https://stoplight.io/mocks/enzonotario/argentine-rock/122547792/api/v1/artists?limit=10&offset=1");
@@ -476,6 +479,7 @@ curl_close($ch);",
           },
           {
             "highlighter": "python",
+            "icon": ".py",
             "label": "Python",
             "lang": "python",
             "source": "requests.get(
@@ -803,6 +807,7 @@ curl_close($ch);",
         "codeSamples": [
           {
             "highlighter": "bash",
+            "icon": "curl",
             "label": "cURL",
             "lang": "curl",
             "source": "curl https://stoplight.io/mocks/enzonotario/argentine-rock/122547792/api/v1/artists \\
@@ -819,6 +824,7 @@ curl_close($ch);",
           },
           {
             "highlighter": "javascript",
+            "icon": ".js",
             "label": "JavaScript",
             "lang": "javascript",
             "source": "fetch('https://stoplight.io/mocks/enzonotario/argentine-rock/122547792/api/v1/artists', {
@@ -838,6 +844,7 @@ curl_close($ch);",
           },
           {
             "highlighter": "php",
+            "icon": ".php",
             "label": "PHP",
             "lang": "php",
             "source": "$ch = curl_init("https://stoplight.io/mocks/enzonotario/argentine-rock/122547792/api/v1/artists");
@@ -858,6 +865,7 @@ curl_close($ch);",
           },
           {
             "highlighter": "python",
+            "icon": ".py",
             "label": "Python",
             "lang": "python",
             "source": "requests.post(
@@ -1424,6 +1432,7 @@ curl_close($ch);",
         "codeSamples": [
           {
             "highlighter": "bash",
+            "icon": "curl",
             "label": "cURL",
             "lang": "curl",
             "source": "curl https://stoplight.io/mocks/enzonotario/argentine-rock/122547792/api/v1/artists/1 \\
@@ -1432,6 +1441,7 @@ curl_close($ch);",
           },
           {
             "highlighter": "javascript",
+            "icon": ".js",
             "label": "JavaScript",
             "lang": "javascript",
             "source": "fetch('https://stoplight.io/mocks/enzonotario/argentine-rock/122547792/api/v1/artists/1', {
@@ -1443,6 +1453,7 @@ curl_close($ch);",
           },
           {
             "highlighter": "php",
+            "icon": ".php",
             "label": "PHP",
             "lang": "php",
             "source": "$ch = curl_init("https://stoplight.io/mocks/enzonotario/argentine-rock/122547792/api/v1/artists/1");
@@ -1455,6 +1466,7 @@ curl_close($ch);",
           },
           {
             "highlighter": "python",
+            "icon": ".py",
             "label": "Python",
             "lang": "python",
             "source": "requests.delete(
@@ -1722,18 +1734,21 @@ curl_close($ch);",
         "codeSamples": [
           {
             "highlighter": "bash",
+            "icon": "curl",
             "label": "cURL",
             "lang": "curl",
             "source": "curl https://stoplight.io/mocks/enzonotario/argentine-rock/122547792/api/v1/artists/1",
           },
           {
             "highlighter": "javascript",
+            "icon": ".js",
             "label": "JavaScript",
             "lang": "javascript",
             "source": "fetch('https://stoplight.io/mocks/enzonotario/argentine-rock/122547792/api/v1/artists/1')",
           },
           {
             "highlighter": "php",
+            "icon": ".php",
             "label": "PHP",
             "lang": "php",
             "source": "$ch = curl_init("https://stoplight.io/mocks/enzonotario/argentine-rock/122547792/api/v1/artists/1");
@@ -1744,6 +1759,7 @@ curl_close($ch);",
           },
           {
             "highlighter": "python",
+            "icon": ".py",
             "label": "Python",
             "lang": "python",
             "source": "requests.get(
@@ -2021,6 +2037,7 @@ curl_close($ch);",
         "codeSamples": [
           {
             "highlighter": "bash",
+            "icon": "curl",
             "label": "cURL",
             "lang": "curl",
             "source": "curl https://stoplight.io/mocks/enzonotario/argentine-rock/122547792/api/v1/artists/1 \\
@@ -2037,6 +2054,7 @@ curl_close($ch);",
           },
           {
             "highlighter": "javascript",
+            "icon": ".js",
             "label": "JavaScript",
             "lang": "javascript",
             "source": "fetch('https://stoplight.io/mocks/enzonotario/argentine-rock/122547792/api/v1/artists/1', {
@@ -2056,6 +2074,7 @@ curl_close($ch);",
           },
           {
             "highlighter": "php",
+            "icon": ".php",
             "label": "PHP",
             "lang": "php",
             "source": "$ch = curl_init("https://stoplight.io/mocks/enzonotario/argentine-rock/122547792/api/v1/artists/1");
@@ -2075,6 +2094,7 @@ curl_close($ch);",
           },
           {
             "highlighter": "python",
+            "icon": ".py",
             "label": "Python",
             "lang": "python",
             "source": "requests.put(
@@ -2621,18 +2641,21 @@ curl_close($ch);",
         "codeSamples": [
           {
             "highlighter": "bash",
+            "icon": "curl",
             "label": "cURL",
             "lang": "curl",
             "source": "curl 'https://stoplight.io/mocks/enzonotario/argentine-rock/122547792/api/v1/artists/1/albums?limit=10&offset=1'",
           },
           {
             "highlighter": "javascript",
+            "icon": ".js",
             "label": "JavaScript",
             "lang": "javascript",
             "source": "fetch('https://stoplight.io/mocks/enzonotario/argentine-rock/122547792/api/v1/artists/1/albums?limit=10&offset=1')",
           },
           {
             "highlighter": "php",
+            "icon": ".php",
             "label": "PHP",
             "lang": "php",
             "source": "$ch = curl_init("https://stoplight.io/mocks/enzonotario/argentine-rock/122547792/api/v1/artists/1/albums?limit=10&offset=1");
@@ -2643,6 +2666,7 @@ curl_close($ch);",
           },
           {
             "highlighter": "python",
+            "icon": ".py",
             "label": "Python",
             "lang": "python",
             "source": "requests.get(
@@ -2951,6 +2975,7 @@ curl_close($ch);",
         "codeSamples": [
           {
             "highlighter": "bash",
+            "icon": "curl",
             "label": "cURL",
             "lang": "curl",
             "source": "curl https://stoplight.io/mocks/enzonotario/argentine-rock/122547792/api/v1/artists/1/albums \\
@@ -2966,6 +2991,7 @@ curl_close($ch);",
           },
           {
             "highlighter": "javascript",
+            "icon": ".js",
             "label": "JavaScript",
             "lang": "javascript",
             "source": "fetch('https://stoplight.io/mocks/enzonotario/argentine-rock/122547792/api/v1/artists/1/albums', {
@@ -2984,6 +3010,7 @@ curl_close($ch);",
           },
           {
             "highlighter": "php",
+            "icon": ".php",
             "label": "PHP",
             "lang": "php",
             "source": "$ch = curl_init("https://stoplight.io/mocks/enzonotario/argentine-rock/122547792/api/v1/artists/1/albums");
@@ -3003,6 +3030,7 @@ curl_close($ch);",
           },
           {
             "highlighter": "python",
+            "icon": ".py",
             "label": "Python",
             "lang": "python",
             "source": "requests.post(
@@ -3502,6 +3530,7 @@ curl_close($ch);",
         "codeSamples": [
           {
             "highlighter": "bash",
+            "icon": "curl",
             "label": "cURL",
             "lang": "curl",
             "source": "curl https://stoplight.io/mocks/enzonotario/argentine-rock/122547792/api/v1/user/signup \\
@@ -3515,6 +3544,7 @@ curl_close($ch);",
           },
           {
             "highlighter": "javascript",
+            "icon": ".js",
             "label": "JavaScript",
             "lang": "javascript",
             "source": "fetch('https://stoplight.io/mocks/enzonotario/argentine-rock/122547792/api/v1/user/signup', {
@@ -3531,6 +3561,7 @@ curl_close($ch);",
           },
           {
             "highlighter": "php",
+            "icon": ".php",
             "label": "PHP",
             "lang": "php",
             "source": "$ch = curl_init("https://stoplight.io/mocks/enzonotario/argentine-rock/122547792/api/v1/user/signup");
@@ -3549,6 +3580,7 @@ curl_close($ch);",
           },
           {
             "highlighter": "python",
+            "icon": ".py",
             "label": "Python",
             "lang": "python",
             "source": "requests.post(