Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changeset/thick-buckets-agree.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"ec-lang-logo": minor
---

Allows for automatic color change from `#fff` to `#000` when the code block is shown with a light theme.
This is achieved by looking at the `html[data-theme]` value.

Also adds another `color` option: `theme` takes the standard code foreground color and applies it to the language logo.
90 changes: 90 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# Language Logo Plugin For Expressive Code

A plugin for Expressive Code that adds language logos (coming from [Simple Icons](https://simpleicons.org)) to code blocks.

## Examples

<img src="./assets/plain.png" alt="example code block with mono color" />
<img src="./assets/original.png" alt="example code block with the original brand color" />
<img src="./assets/light.png" alt="example code block with a light theme" />

For more examples, see [my blog post](http://blog.dertimonius.dev/posts/til-38).

## Installation

```bash
npm install ec-lang-logo
# pnpm install ec-lang-logo
# bun install ec-lang-logo
# yarn add ec-lang-logo
```

## Usage

Add the plugin to your Expressive Code configuration:

```js
import { defineConfig } from 'astro/config';
import { pluginLanguageLogo } from 'ec-lang-logo';

export default defineConfig({
integrations: [
starlight({
expressiveCode: {
plugins: [pluginLanguageLogo()],
},
}),
],
});
```

## Configuration

The plugin accepts an optional configuration object:

```ts
pluginLanguageLogo({
color: 'mono', // 'mono' | 'original' | 'theme' | '#hexcolor'
excludedLangs: [], // Array of language identifiers to exclude
})
```

### Options

- `color`: Controls the badge color
- `'mono'`: Adapts to theme (white for dark, black for light)
- `'original'`: Uses the official language brand color
- `'theme'`: Takes the code foreground color of the currently active theme
- `'#hexcolor'`: Custom hex color (e.g., `'#ff0000'`)
- `excludedLangs`: Array of language identifiers where badges should not appear

## Per-Block Customization

You can override settings for individual code blocks using meta attributes:

```md
```js badge-color="#ff0000"
// Custom red badge
```

```md
```js badge-color="original"
// Shows the official brand color
```

```md
```ts hide-badge
// No badge on this block
```

```

### Available Meta Attributes

- `badge-color="value"`: Override the badge color for this block (takes the same values as the overall options)
- `hide-badge`: Hide the badge for this block

## Theme Support

The plugin automatically adapts mono-colored badges to your site's theme. Ensure your theme switcher uses `data-theme="light"` or `data-theme="dark"` on the `html` element.

Binary file added assets/light.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/original.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/plain.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
43 changes: 32 additions & 11 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ const LANGUAGE_MAP = {
yml: { icon: icons.siYaml },
yaml: { icon: icons.siYaml },
json: { icon: icons.siJson },
graphql: { icon: icons.siGraphql },
gql: { icon: icons.siGraphql },

html: { icon: icons.siHtml5 },
css: { icon: icons.siCss },
Expand All @@ -47,8 +49,10 @@ const LANGUAGE_MAP = {

type Language = keyof typeof LANGUAGE_MAP;

type Color = 'mono' | 'original' | 'theme' | (`#${string}` & {});

interface PluginOptions {
color: 'mono' | 'original' | (`#${string}` & {});
color: Color;
excludedLangs: Language[];
}

Expand All @@ -63,6 +67,14 @@ export function pluginLanguageLogo(config?: PluginOptions) {
return definePlugin({
name: 'Language Logo',
baseStyles: `
html[data-theme=light] {
--ec-lang-logo: #000;
}

html[data-theme=dark] {
--ec-lang-logo: #eee;
}

.ec-lang-logo {
position: absolute;
width: 48px;
Expand All @@ -77,7 +89,7 @@ export function pluginLanguageLogo(config?: PluginOptions) {
pointer-events: none;
z-index: 10;
}
[data-small=true] {
[data-ec-logo-small=true] {
width: 32px;
height: 32px;
bottom: 0.8rem;
Expand Down Expand Up @@ -106,16 +118,17 @@ export function pluginLanguageLogo(config?: PluginOptions) {
if (!foundLang) {
return;
}

const loc = codeBlock.getLines().length;
const icon = foundLang.icon;
const color = colorMatch ? colorMatch[1] || colorMatch[2] : opts.color;
const color = colorMatch
? ((colorMatch[1] || colorMatch[2]) as Color)
: opts.color;

const logoSvg = h(
'div',
{
class: 'ec-lang-logo',
'data-small': loc === 1 ? 'true' : undefined,
'data-ec-logo-small': loc === 1 ? 'true' : undefined,
},
[
h(
Expand All @@ -125,12 +138,7 @@ export function pluginLanguageLogo(config?: PluginOptions) {
viewBox: '0 0 24 24',
width: loc === 1 ? '24px' : '32px',
height: loc === 1 ? '24px' : '32px',
fill:
color === 'mono'
? '#fff'
: color === 'original'
? `#${icon.hex}`
: color,
fill: getFillColor(color, icon.hex),
},
[
h('title', { innerHtml: icon.title }),
Expand All @@ -147,3 +155,16 @@ export function pluginLanguageLogo(config?: PluginOptions) {
},
});
}

function getFillColor(color: Color, iconHex: string): string {
if (color === 'mono') {
return 'var(--ec-lang-logo)';
}
if (color === 'theme') {
return 'var(--ec-codeFg)';
}
if (color === 'original') {
return `#${iconHex}`;
}
return color;
}
Loading