├── .changeset ├── README.md └── config.json ├── .github └── workflows │ ├── deploy-docs.yml │ └── release.yml ├── .gitignore ├── .npmrc ├── README.md ├── docs ├── .gitignore ├── README.md ├── astro.config.mjs ├── ec.config.mjs ├── package.json ├── public │ ├── favicon.svg │ └── og.png ├── src │ ├── content │ │ ├── config.ts │ │ └── docs │ │ │ ├── configuration.mdx │ │ │ ├── getting-started.mdx │ │ │ └── index.mdx │ ├── env.d.ts │ └── styles.css └── tsconfig.json ├── package.json ├── packages └── expressive-code-color-chips │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ ├── colorRegEx.ts │ └── index.ts │ └── tsconfig.json ├── pnpm-lock.yaml └── pnpm-workspace.yaml /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@3.0.3/schema.json", 3 | "changelog": ["@changesets/changelog-github", { "repo": "delucis/expressive-code-color-chips" }], 4 | "commit": false, 5 | "fixed": [], 6 | "linked": [], 7 | "access": "public", 8 | "baseBranch": "main", 9 | "updateInternalDependencies": "patch", 10 | "ignore": ["@expressive-code-color-chips/*"] 11 | } 12 | -------------------------------------------------------------------------------- /.github/workflows/deploy-docs.yml: -------------------------------------------------------------------------------- 1 | name: Deploy to GitHub Pages 2 | 3 | on: 4 | # Trigger the workflow every time you push to the `main` branch 5 | # Using a different branch name? Replace `main` with your branch’s name 6 | push: 7 | branches: [main] 8 | # Allows you to run this workflow manually from the Actions tab on GitHub. 9 | workflow_dispatch: 10 | 11 | # Allow this job to clone the repo and create a page deployment 12 | permissions: 13 | contents: read 14 | pages: write 15 | id-token: write 16 | 17 | jobs: 18 | build: 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: actions/checkout@v4 22 | - uses: pnpm/action-setup@v3 23 | - uses: actions/setup-node@v4 24 | with: 25 | node-version: 20 26 | cache: pnpm 27 | - run: pnpm i 28 | - run: pnpm build 29 | - run: pnpm build:docs 30 | - uses: actions/upload-pages-artifact@v3 31 | with: 32 | path: docs/dist/ 33 | 34 | deploy: 35 | needs: build 36 | runs-on: ubuntu-latest 37 | environment: 38 | name: github-pages 39 | url: ${{ steps.deployment.outputs.page_url }} 40 | steps: 41 | - name: Deploy to GitHub Pages 42 | id: deployment 43 | uses: actions/deploy-pages@v4 44 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | release: 10 | name: Release 11 | if: ${{ github.repository_owner == 'delucis' }} 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | with: 16 | # This makes Actions fetch all Git history so that Changesets can generate changelogs with the correct commits 17 | fetch-depth: 0 18 | 19 | - uses: pnpm/action-setup@v3 20 | 21 | - uses: actions/setup-node@v4 22 | with: 23 | node-version: 20 24 | cache: pnpm 25 | 26 | - run: pnpm i 27 | 28 | - uses: changesets/action@v1 29 | with: 30 | version: pnpm run ci-version 31 | publish: pnpm run ci-publish 32 | commit: '[ci] release' 33 | title: '[ci] release' 34 | env: 35 | GITHUB_TOKEN: ${{ secrets.CHRISBOT_GITHUB_TOKEN }} 36 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | node_modules/ 3 | 4 | # logs 5 | npm-debug.log* 6 | yarn-debug.log* 7 | yarn-error.log* 8 | pnpm-debug.log* 9 | 10 | # environment variables 11 | .env 12 | .env.production 13 | 14 | # macOS-specific files 15 | .DS_Store 16 | 17 | # build output 18 | dist/ 19 | 20 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | prefer-workspace-packages=true 2 | link-workspace-packages=true 3 | 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | packages/expressive-code-color-chips/README.md -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | # build output 2 | dist/ 3 | # generated types 4 | .astro/ 5 | 6 | # dependencies 7 | node_modules/ 8 | 9 | # logs 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | pnpm-debug.log* 14 | 15 | 16 | # environment variables 17 | .env 18 | .env.production 19 | 20 | # macOS-specific files 21 | .DS_Store 22 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Expressive Code Color Chips Docs 2 | 3 | [![Built with Starlight](https://astro.badg.es/v2/built-with-starlight/tiny.svg)](https://starlight.astro.build) 4 | 5 | This directory contains the documentation website. 6 | 7 | ## 🧞 Commands 8 | 9 | In this directory, you can run these commands from a terminal: 10 | 11 | | Command | Action | 12 | | :------------------------- | :----------------------------------------------- | 13 | | `pnpm install` | Installs dependencies | 14 | | `pnpm run dev` | Starts local dev server at `localhost:4321` | 15 | | `pnpm run build` | Build your production site to `./dist/` | 16 | | `pnpm run preview` | Preview your build locally, before deploying | 17 | | `pnpm run astro ...` | Run CLI commands like `astro add`, `astro check` | 18 | | `pnpm run astro -- --help` | Get help using the Astro CLI | 19 | -------------------------------------------------------------------------------- /docs/astro.config.mjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | import { defineConfig } from 'astro/config'; 3 | import starlight from '@astrojs/starlight'; 4 | 5 | // https://astro.build/config 6 | export default defineConfig({ 7 | site: 'https://delucis.github.io', 8 | base: '/expressive-code-color-chips', 9 | integrations: [ 10 | starlight({ 11 | title: 'Expressive Code Color Chips', 12 | description: 13 | 'CSS color preview plugin for Expressive Code. Display a small sample of each CSS color in your code examples.', 14 | social: { 15 | github: 'https://github.com/delucis/expressive-code-color-chips', 16 | }, 17 | sidebar: ['getting-started', 'configuration'], 18 | customCss: ['./src/styles.css'], 19 | head: [ 20 | { 21 | tag: 'meta', 22 | attrs: { 23 | property: 'og:image', 24 | content: 'https://delucis.github.io/expressive-code-color-chips/og.png', 25 | }, 26 | }, 27 | ], 28 | }), 29 | ], 30 | }); 31 | -------------------------------------------------------------------------------- /docs/ec.config.mjs: -------------------------------------------------------------------------------- 1 | import { defineEcConfig } from '@astrojs/starlight/expressive-code'; 2 | import { pluginColorChips } from 'expressive-code-color-chips'; 3 | 4 | export default defineEcConfig({ 5 | plugins: [pluginColorChips()], 6 | styleOverrides: { 7 | colorChips: { 8 | // borderRadius: 0, 9 | }, 10 | }, 11 | }); 12 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@expressive-code-color-chips/docs", 3 | "type": "module", 4 | "private": true, 5 | "version": "0.0.1", 6 | "scripts": { 7 | "dev": "astro dev", 8 | "start": "astro dev", 9 | "build": "astro check && astro build", 10 | "preview": "astro preview", 11 | "astro": "astro" 12 | }, 13 | "dependencies": { 14 | "@astrojs/check": "^0.9.4", 15 | "@astrojs/starlight": "^0.28.3", 16 | "astro": "^4.16.18", 17 | "expressive-code-color-chips": "*", 18 | "sharp": "^0.32.5", 19 | "starlight-package-managers": "^0.7.0", 20 | "typescript": "^5.6.3" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /docs/public/favicon.svg: -------------------------------------------------------------------------------- 1 | 🎨 -------------------------------------------------------------------------------- /docs/public/og.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/delucis/expressive-code-color-chips/71e97da90084190f3ddb01ea61edd052395f2175/docs/public/og.png -------------------------------------------------------------------------------- /docs/src/content/config.ts: -------------------------------------------------------------------------------- 1 | import { defineCollection } from 'astro:content'; 2 | import { docsSchema } from '@astrojs/starlight/schema'; 3 | 4 | export const collections = { 5 | docs: defineCollection({ schema: docsSchema() }), 6 | }; 7 | -------------------------------------------------------------------------------- /docs/src/content/docs/configuration.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Configuration 3 | description: Optional configuration for the Expressive Code Color Chips plugin, including how to customize styling. 4 | --- 5 | 6 | After adding the Color Chips plugin to your Expressive Code config as shown in [the set-up guide](/expressive-code-color-chips/getting-started/), no further configuration is required. 7 | 8 | If you want to customize the appearance of the color chips, you can use style overrides. 9 | 10 | ## Styling 11 | 12 | You can provide custom styles in your Expressive Code config using the `styleOverrides.colorChips` object. 13 | 14 | For example, to display square chips instead of the default round style, set `borderRadius` to zero: 15 | 16 | ```js 17 | { 18 | plugins: [pluginColorChips()], 19 | styleOverrides: { 20 | colorChips: { 21 | borderRadius: 0, 22 | }, 23 | }, 24 | }, 25 | ``` 26 | 27 | ## Available style overrides 28 | 29 | ### `size` 30 | 31 | **Default:** `"1.2em"` 32 | 33 | The size of each color chip. 34 | 35 | ### `borderWidth` 36 | 37 | **Default:** `"1px"` 38 | 39 | The width of the border around each color chip. 40 | 41 | ### `borderRadius` 42 | 43 | **Default:** `"50%"` 44 | 45 | The roundness of each color chip. 46 | 47 | ### `borderColor` 48 | 49 | **Default:** `({ theme }) => theme.fg` 50 | 51 | The color of the border around each chip. 52 | 53 | ### `transparencyShadeOne` 54 | 55 | **Default:** `["#777", "#fff"]` 56 | 57 | The first of two shades used in the checkerboard background for transparent colors. 58 | 59 | ### `transparencyShadeTwo` 60 | 61 | **Default:** `["#000", "#bbb"]` 62 | 63 | The second of two shades used in the checkerboard background for transparent colors. 64 | -------------------------------------------------------------------------------- /docs/src/content/docs/getting-started.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Getting Started 3 | description: How to install and use the Expressive Code Color Chips plugin in your website. 4 | --- 5 | 6 | The Color Chips plugin extends [Expressive Code](https://expressive-code.com/) to display a preview “chip” for colors in CSS code blocks. 7 | This guide will show you how to add it to your website. 8 | 9 | ## Set-up 10 | 11 | import { Steps, Tabs, TabItem } from '@astrojs/starlight/components'; 12 | import { PackageManagers } from 'starlight-package-managers'; 13 | 14 | 15 | 16 | 1. Install the `expressive-code-color-chips` dependency using your preferred package manager: 17 | 18 | 19 | 20 | 2. Add the plugin to your site’s Expressive Code configuration: 21 | 22 | 23 | 24 | 25 | 26 | ```js title="astro.config.mjs" ins={3,8} 27 | import { defineConfig } from 'astro/config'; 28 | import astroExpressiveCode from 'astro-expressive-code'; 29 | import { pluginColorChips } from 'expressive-code-color-chips'; 30 | 31 | export default defineConfig({ 32 | integrations: [ 33 | astroExpressiveCode({ 34 | plugins: [pluginColorChips()], 35 | }), 36 | ], 37 | }); 38 | ``` 39 | 40 | 41 | 42 | 43 | 44 | ```js title="astro.config.mjs" ins={3,10} 45 | import { defineConfig } from 'astro/config'; 46 | import starlight from '@astrojs/starlight'; 47 | import { pluginColorChips } from 'expressive-code-color-chips'; 48 | 49 | export default defineConfig({ 50 | integrations: [ 51 | starlight({ 52 | title: 'My Starlight site', 53 | expressiveCode: { 54 | plugins: [pluginColorChips()], 55 | }, 56 | }), 57 | ], 58 | }); 59 | ``` 60 | 61 | 62 | 63 | 64 | 65 | ```js title="next.config.mjs" ins={3,7} 66 | import createMDX from '@next/mdx'; 67 | import rehypeExpressiveCode from 'rehype-expressive-code'; 68 | import { pluginColorChips } from 'expressive-code-color-chips'; 69 | 70 | /** @type {import('rehype-expressive-code').RehypeExpressiveCodeOptions} */ 71 | const rehypeExpressiveCodeOptions = { 72 | plugins: [pluginColorChips()], 73 | }; 74 | 75 | /** @type {import('next').NextConfig} */ 76 | const nextConfig = { 77 | reactStrictMode: true, 78 | pageExtensions: ['js', 'jsx', 'ts', 'tsx', 'md', 'mdx'], 79 | }; 80 | 81 | const withMDX = createMDX({ 82 | extension: /\.mdx?$/, 83 | options: { 84 | remarkPlugins: [], 85 | rehypePlugins: [ 86 | // The nested array structure is required to pass options 87 | // to a rehype plugin 88 | [rehypeExpressiveCode, rehypeExpressiveCodeOptions], 89 | ], 90 | }, 91 | }); 92 | 93 | export default withMDX(nextConfig); 94 | ``` 95 | 96 | 97 | 98 | 99 | 100 | ```js title="ec.config.mjs" ins={1,4} 101 | import { pluginColorChips } from 'expressive-code-color-chips'; 102 | 103 | export default { 104 | plugins: [pluginColorChips()], 105 | }; 106 | ``` 107 | 108 | 109 | 110 | 111 | 112 | 3. That’s it! Colors in CSS code blocks will now be annotated with a small preview. 113 | 114 | 115 | 116 | ## Usage 117 | 118 | There is no specific syntax for this plugin. 119 | It detects CSS color syntax in code blocks tagged with `css` and annotates all valid colors it finds. 120 | 121 | For example the following Markdown code: 122 | 123 | ````md 124 | ```css 125 | .example { 126 | color: goldenrod; 127 | } 128 | ``` 129 | ```` 130 | 131 | Renders a code block with a chip next to the color `goldenrod`: 132 | 133 | ```css 134 | .example { 135 | color: goldenrod; 136 | } 137 | ``` 138 | 139 | In addition to `css`, this plugin also runs for `scss`, `sass`, `less`, and `stylus` code blocks. 140 | For example: 141 | 142 | ````md 143 | ```scss 144 | $primary: fuchsia; 145 | ``` 146 | ```` 147 | 148 | Which renders as: 149 | 150 | ```scss 151 | $primary: fuchsia; 152 | ``` 153 | 154 | ## Support 155 | 156 | This is open-source software. 157 | If you run into any bugs, please [report issues on GitHub](https://github.com/delucis/expressive-code-color-chips/issues). 158 | -------------------------------------------------------------------------------- /docs/src/content/docs/index.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Expressive Code Color Chips 3 | description: Add color previews to your CSS code examples with the Expressive Code Color Chips plugin. 4 | template: splash 5 | hero: 6 | tagline: A plugin to add color previews to your CSS code examples 7 | image: 8 | html: 🎨 9 | actions: 10 | - text: Get started 11 | link: /expressive-code-color-chips/getting-started/ 12 | icon: right-arrow 13 | - text: Star on GitHub 14 | link: https://github.com/delucis/expressive-code-color-chips 15 | icon: star 16 | variant: minimal 17 | head: 18 | - tag: style 19 | content: >- 20 | .hero-html { 21 | --size: min(10rem, calc(5rem + 6vw)); 22 | --blur: calc(var(--size) / 3.5); 23 | --x-shift: calc(var(--size) / 3); 24 | --y-shift: calc(var(--size) / 5); 25 | font-size: var(--size); 26 | line-height: var(--size); 27 | justify-content: center; 28 | filter: 29 | drop-shadow(0 0 var(--blur) var(--sl-color-blue)) 30 | drop-shadow(calc(-1 * var(--x-shift)) var(--y-shift) var(--blur) var(--sl-color-purple)) 31 | drop-shadow(var(--x-shift) calc(-1 * var(--y-shift)) var(--blur) var(--sl-color-green)); 32 | z-index: -1; 33 | } 34 | [data-has-hero] header { 35 | border-color: transparent; 36 | background-color: transparent; 37 | backdrop-filter: blur(1rem); 38 | } 39 | .hero h1 { text-wrap: balance } 40 | --- 41 | 42 | ```css title="example.css" 43 | .chips { 44 | /* CSS named colors */ 45 | color: red; 46 | /* Hexadecimal colors */ 47 | background-color: #fff; 48 | /* HSL color functions */ 49 | border-color: hsl(0, 0%, 0%); 50 | /* System colors */ 51 | outline-color: SelectedItem; 52 | /* Transparent colors */ 53 | background: linear-gradient(rgba(0, 0, 255, 0.25), rgba(0, 0, 255, 0.75)); 54 | /* And more… */ 55 | --more: oklch(70% 0.1 72); 56 | } 57 | ``` 58 | -------------------------------------------------------------------------------- /docs/src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /docs/src/styles.css: -------------------------------------------------------------------------------- 1 | /* Dark mode colors. */ 2 | :root { 3 | --sl-color-accent-low: #242424; 4 | --sl-color-accent: #6a6a6a; 5 | --sl-color-accent-high: #fff; 6 | --sl-color-white: #ffffff; 7 | --sl-color-gray-1: #eeeeee; 8 | --sl-color-gray-2: #c2c2c2; 9 | --sl-color-gray-3: #8b8b8b; 10 | --sl-color-gray-4: #585858; 11 | --sl-color-gray-5: #383838; 12 | --sl-color-gray-6: #272727; 13 | --sl-color-black: #181818; 14 | } 15 | /* Light mode colors. */ 16 | :root[data-theme='light'] { 17 | --sl-color-accent-low: #d7d7d7; 18 | --sl-color-accent: #181818; 19 | --sl-color-accent-high: #000; 20 | --sl-color-white: #181818; 21 | --sl-color-gray-1: #272727; 22 | --sl-color-gray-2: #383838; 23 | --sl-color-gray-3: #585858; 24 | --sl-color-gray-4: #8b8b8b; 25 | --sl-color-gray-5: #c2c2c2; 26 | --sl-color-gray-6: #eeeeee; 27 | --sl-color-gray-7: #f6f6f6; 28 | --sl-color-black: #ffffff; 29 | } 30 | -------------------------------------------------------------------------------- /docs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "astro/tsconfigs/strict", 3 | "exclude": ["dist/*"] 4 | } 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@expressive-code-color-chips/root", 3 | "version": "0.0.0", 4 | "private": true, 5 | "author": "delucis", 6 | "license": "MIT", 7 | "packageManager": "pnpm@9.12.1", 8 | "devDependencies": { 9 | "tsup": "^8.3.5" 10 | }, 11 | "dependencies": { 12 | "@changesets/changelog-github": "^0.5.0", 13 | "@changesets/cli": "^2.27.9" 14 | }, 15 | "scripts": { 16 | "build": "pnpm -F expressive-code-color-chips build", 17 | "build:docs": "pnpm -F @expressive-code-color-chips/docs build", 18 | "ci-version": "changeset version && pnpm install --no-frozen-lockfile", 19 | "ci-publish": "pnpm build && changeset publish" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/expressive-code-color-chips/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # expressive-code-color-chips 2 | 3 | ## 0.1.2 4 | 5 | ### Patch Changes 6 | 7 | - [`75369fd`](https://github.com/delucis/expressive-code-color-chips/commit/75369fda4077abf4dff9a25d49adad443ccd320e) Thanks [@delucis](https://github.com/delucis)! - Updates `package.json` metadata 8 | 9 | ## 0.1.1 10 | 11 | ### Patch Changes 12 | 13 | - [`8cb8982`](https://github.com/delucis/expressive-code-color-chips/commit/8cb89821731d98ecaa6d5fffc91963b506f68edb) Thanks [@delucis](https://github.com/delucis)! - Avoids annotating named colors inside CSS comments as this can lead to false positives 14 | 15 | ## 0.1.0 16 | 17 | ### Minor Changes 18 | 19 | - [`90f3386`](https://github.com/delucis/expressive-code-color-chips/commit/90f3386ac1077ed415533d527acc52c68597b785) Thanks [@delucis](https://github.com/delucis)! - Initial release 20 | -------------------------------------------------------------------------------- /packages/expressive-code-color-chips/README.md: -------------------------------------------------------------------------------- 1 | # Expressive Code Color Chips 2 | 3 | An Expressive Code plugin to add color previews to your syntax highlighted CSS code examples. 4 | 5 | ![Code example showing round color samples next to a range of CSS color syntaxes](https://raw.githubusercontent.com/delucis/expressive-code-color-chips/refs/heads/main/docs/public/og.png) 6 | 7 | ## Documentation 8 | 9 | [Read the full documentation →](https://delucis.github.io/expressive-code-color-chips/) 10 | 11 | ## License 12 | 13 | MIT 14 | -------------------------------------------------------------------------------- /packages/expressive-code-color-chips/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "expressive-code-color-chips", 3 | "description": "CSS color preview plugin for Expressive Code. Display a small sample of each CSS color in your syntax highlighted code examples.", 4 | "version": "0.1.2", 5 | "keywords": [ 6 | "expressive-code", 7 | "withastro", 8 | "syntax-highlighting" 9 | ], 10 | "author": "Chris Swithinbank ", 11 | "homepage": "https://delucis.github.io/expressive-code-color-chips/", 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/delucis/expressive-code-color-chips", 15 | "directory": "packages/expressive-code-color-chips" 16 | }, 17 | "bugs": "https://github.com/delucis/expressive-code-color-chips/issues", 18 | "license": "MIT", 19 | "type": "module", 20 | "main": "./dist/index.js", 21 | "module": "./dist/index.js", 22 | "exports": { 23 | "types": "./dist/index.d.ts", 24 | "default": "./dist/index.js" 25 | }, 26 | "types": "./dist/index.d.ts", 27 | "files": [ 28 | "dist" 29 | ], 30 | "scripts": { 31 | "build": "tsup ./src/index.ts --format esm --dts --sourcemap --clean", 32 | "watch": "pnpm build --watch src" 33 | }, 34 | "devDependencies": { 35 | "@expressive-code/core": "^0.37.1" 36 | }, 37 | "peerDependencies": { 38 | "@expressive-code/core": "^0.37.1" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /packages/expressive-code-color-chips/src/colorRegEx.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Match all CSS color syntaxes, including named and system colors. 3 | * 4 | * Based on [`Kyza/color-regex`](https://github.com/Kyza/color-regex/blob/trunk/index.reg), 5 | * but modified to avoid matching `currentColor`, which is contextual so can’t be displayed. 6 | * 7 | * MIT License 8 | * 9 | * Copyright (c) 2023 Kyza 10 | * 11 | * Permission is hereby granted, free of charge, to any person obtaining a copy 12 | * of this software and associated documentation files (the "Software"), to deal 13 | * in the Software without restriction, including without limitation the rights 14 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | * copies of the Software, and to permit persons to whom the Software is 16 | * furnished to do so, subject to the following conditions: 17 | * 18 | * The above copyright notice and this permission notice shall be included in all 19 | * copies or substantial portions of the Software. 20 | * 21 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | * SOFTWARE. 28 | */ 29 | export const colors = { 30 | /** Match all CSS color syntaxes, excluding named and system colors. */ 31 | programmatic: 32 | /(#)(?:([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})?|([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])?)|(rgb|rgba)\((?:\s*(0*(?:0|1[0-9]{0,2}|2(?:[0-4][0-9]?|5[0-4]?|[6-9])?|[3-9][0-9]?)(?:\.[0-9]+)?|255(?:\.0+)?|\.[0-9]+)\s*,\s*(0*(?:0|1[0-9]{0,2}|2(?:[0-4][0-9]?|5[0-4]?|[6-9])?|[3-9][0-9]?)(?:\.[0-9]+)?|255(?:\.0+)?|\.[0-9]+)\s*,\s*(0*(?:0|1[0-9]{0,2}|2(?:[0-4][0-9]?|5[0-4]?|[6-9])?|[3-9][0-9]?)(?:\.[0-9]+)?|255(?:\.0+)?|\.[0-9]+)(?:\s*,\s*(0*(?:(?:0|[1-9][0-9]?)(?:\.[0-9]+)?|100(?:\.0+)?|\.[0-9]+)%|0*0*(?:\.[0-9]+)?|1(?:\.0+)?|\.[0-9]+))?\s*|\s*(0*(?:0|1[0-9]{0,2}|2(?:[0-4][0-9]?|5[0-4]?|[6-9])?|[3-9][0-9]?)(?:\.[0-9]+)?|255(?:\.0+)?|\.[0-9]+)\s+(0*(?:0|1[0-9]{0,2}|2(?:[0-4][0-9]?|5[0-4]?|[6-9])?|[3-9][0-9]?)(?:\.[0-9]+)?|255(?:\.0+)?|\.[0-9]+)\s+(0*(?:0|1[0-9]{0,2}|2(?:[0-4][0-9]?|5[0-4]?|[6-9])?|[3-9][0-9]?)(?:\.[0-9]+)?|255(?:\.0+)?|\.[0-9]+)\s*|\s*(0*(?:(?:0|[1-9][0-9]?)(?:\.[0-9]+)?|100(?:\.0+)?|\.[0-9]+)%)\s*,\s*(0*(?:(?:0|[1-9][0-9]?)(?:\.[0-9]+)?|100(?:\.0+)?|\.[0-9]+)%)\s*,\s*(0*(?:(?:0|[1-9][0-9]?)(?:\.[0-9]+)?|100(?:\.0+)?|\.[0-9]+)%)(?:\s*,\s*(0*(?:(?:0|[1-9][0-9]?)(?:\.[0-9]+)?|100(?:\.0+)?|\.[0-9]+)%|0*0*(?:\.[0-9]+)?|1(?:\.0+)?|\.[0-9]+))?\s*|\s*(0*(?:(?:0|[1-9][0-9]?)(?:\.[0-9]+)?|100(?:\.0+)?|\.[0-9]+)%)\s+(0*(?:(?:0|[1-9][0-9]?)(?:\.[0-9]+)?|100(?:\.0+)?|\.[0-9]+)%)\s+(0*(?:(?:0|[1-9][0-9]?)(?:\.[0-9]+)?|100(?:\.0+)?|\.[0-9]+)%)\s*|\s*(0*(?:0|1[0-9]{0,2}|2(?:[0-4][0-9]?|5[0-4]?|[6-9])?|[3-9][0-9]?)(?:\.[0-9]+)?|255(?:\.0+)?|\.[0-9]+)\s+(0*(?:0|1[0-9]{0,2}|2(?:[0-4][0-9]?|5[0-4]?|[6-9])?|[3-9][0-9]?)(?:\.[0-9]+)?|255(?:\.0+)?|\.[0-9]+)\s+(0*(?:0|1[0-9]{0,2}|2(?:[0-4][0-9]?|5[0-4]?|[6-9])?|[3-9][0-9]?)(?:\.[0-9]+)?|255(?:\.0+)?|\.[0-9]+)(?:\s*(?:\/)\s*(0*(?:(?:0|[1-9][0-9]?)(?:\.[0-9]+)?|100(?:\.0+)?|\.[0-9]+)%|0*0*(?:\.[0-9]+)?|1(?:\.0+)?|\.[0-9]+))?\s*|\s*(0*(?:(?:0|[1-9][0-9]?)(?:\.[0-9]+)?|100(?:\.0+)?|\.[0-9]+)%)\s+(0*(?:(?:0|[1-9][0-9]?)(?:\.[0-9]+)?|100(?:\.0+)?|\.[0-9]+)%)\s+(0*(?:(?:0|[1-9][0-9]?)(?:\.[0-9]+)?|100(?:\.0+)?|\.[0-9]+)%)(?:\s*(?:\/)\s*(0*(?:(?:0|[1-9][0-9]?)(?:\.[0-9]+)?|100(?:\.0+)?|\.[0-9]+)%|0*0*(?:\.[0-9]+)?|1(?:\.0+)?|\.[0-9]+))?\s*)\)|(hsl|hsla)\((?:\s*(-?[0-9]+(?:\.[0-9]+)?(?:deg|rad|grad|turn)?)\s+(0*(?:(?:0|[1-9][0-9]?)(?:\.[0-9]+)?|100(?:\.0+)?|\.[0-9]+)%)\s+(0*(?:(?:0|[1-9][0-9]?)(?:\.[0-9]+)?|100(?:\.0+)?|\.[0-9]+)%)(?:\s*(?:\/)\s*(0*(?:(?:0|[1-9][0-9]?)(?:\.[0-9]+)?|100(?:\.0+)?|\.[0-9]+)%|0*0*(?:\.[0-9]+)?|1(?:\.0+)?|\.[0-9]+))?\s*|\s*(-?[0-9]+(?:\.[0-9]+)?(?:deg|rad|grad|turn)?)\s*,\s*(0*(?:(?:0|[1-9][0-9]?)(?:\.[0-9]+)?|100(?:\.0+)?|\.[0-9]+)%)\s*,\s*(0*(?:(?:0|[1-9][0-9]?)(?:\.[0-9]+)?|100(?:\.0+)?|\.[0-9]+)%)(?:\s*,\s*(0*(?:(?:0|[1-9][0-9]?)(?:\.[0-9]+)?|100(?:\.0+)?|\.[0-9]+)%|0*0*(?:\.[0-9]+)?|1(?:\.0+)?|\.[0-9]+))?\s*|\s*(-?[0-9]+(?:\.[0-9]+)?(?:deg|rad|grad|turn)?)\s+(0*(?:(?:0|[1-9][0-9]?)(?:\.[0-9]+)?|100(?:\.0+)?|\.[0-9]+)%)\s+(0*(?:(?:0|[1-9][0-9]?)(?:\.[0-9]+)?|100(?:\.0+)?|\.[0-9]+)%)\s*)\)|(hwb)\(\s*(-?[0-9]+(?:\.[0-9]+)?(?:deg|rad|grad|turn)?)\s+(0*(?:(?:0|[1-9][0-9]?)(?:\.[0-9]+)?|100(?:\.0+)?|\.[0-9]+)%)\s+(0*(?:(?:0|[1-9][0-9]?)(?:\.[0-9]+)?|100(?:\.0+)?|\.[0-9]+)%)(?:(?:\s*(?:\/)\s*(0*(?:(?:0|[1-9][0-9]?)(?:\.[0-9]+)?|100(?:\.0+)?|\.[0-9]+)%|0*0*(?:\.[0-9]+)?|1(?:\.0+)?|\.[0-9]+))?\s*)?\)|(lab|oklab)\(\s*(0*(?:(?:0|[1-9][0-9]?)(?:\.[0-9]+)?|100(?:\.0+)?|\.[0-9]+)%|0*(?:0|[1-9][0-9]?)(?:\.[0-9]+)?|100(?:\.0+)?)\s+(-?(?:0*(?:(?:0|[1-9][0-9]?)(?:\.[0-9]+)?|100(?:\.0+)?|\.[0-9]+)%|(?:0|1(?:[0-1][0-9]?|2[0-4]?|[3-9])?|[2-9][0-9]?)(?:\.[0-9]+)?|125(?:\.0+)?))\s+(-?(?:0*(?:(?:0|[1-9][0-9]?)(?:\.[0-9]+)?|100(?:\.0+)?|\.[0-9]+)%|(?:0|1(?:[0-1][0-9]?|2[0-4]?|[3-9])?|[2-9][0-9]?)(?:\.[0-9]+)?|125(?:\.0+)?))\s*(?:(?:\s*(?:\/)\s*(0*(?:(?:0|[1-9][0-9]?)(?:\.[0-9]+)?|100(?:\.0+)?|\.[0-9]+)%|0*0*(?:\.[0-9]+)?|1(?:\.0+)?|\.[0-9]+))?\s*)?\)|(lch|oklch)\(\s*(0*(?:(?:0|[1-9][0-9]?)(?:\.[0-9]+)?|100(?:\.0+)?|\.[0-9]+)%|0*(?:0|[1-9][0-9]?)(?:\.[0-9]+)?|100(?:\.0+)?)\s+(0*(?:(?:0|[1-9][0-9]?)(?:\.[0-9]+)?|100(?:\.0+)?|\.[0-9]+)%|(?:0|1(?:[0-4][0-9]?|[5-9])?|[2-9][0-9]?)(?:\.[0-9]+)?|150(?:\.0+)?)\s+(-?[0-9]+(?:\.[0-9]+)?(?:deg|rad|grad|turn)?)\s*(?:(?:\s*(?:\/)\s*(0*(?:(?:0|[1-9][0-9]?)(?:\.[0-9]+)?|100(?:\.0+)?|\.[0-9]+)%|0*0*(?:\.[0-9]+)?|1(?:\.0+)?|\.[0-9]+))?\s*)?\)|(color)\((?:(srgb|srgb-linear|display-p3|a98-rgb|prophoto-rgb|rec2020)(?:\s+|\s*,\s*)(0*0*(?:\.[0-9]+)?|1(?:\.0+)?|\.[0-9]+|0*(?:(?:0|[1-9][0-9]?)(?:\.[0-9]+)?|100(?:\.0+)?|\.[0-9]+)%)(?:\s+|\s*,\s*)(0*0*(?:\.[0-9]+)?|1(?:\.0+)?|\.[0-9]+|0*(?:(?:0|[1-9][0-9]?)(?:\.[0-9]+)?|100(?:\.0+)?|\.[0-9]+)%)(?:\s+|\s*,\s*)(0*0*(?:\.[0-9]+)?|1(?:\.0+)?|\.[0-9]+|0*(?:(?:0|[1-9][0-9]?)(?:\.[0-9]+)?|100(?:\.0+)?|\.[0-9]+)%)(?:(?:\s+\s*(?:\/)\s*(0*(?:(?:0|[1-9][0-9]?)(?:\.[0-9]+)?|100(?:\.0+)?|\.[0-9]+)%|0*0*(?:\.[0-9]+)?|1(?:\.0+)?|\.[0-9]+))?\s*)?|(xyz|xyz-d50|xyz-d65)(?:\s+|\s*,\s*)(-?[0-9]+(?:\.[0-9]+)?%?)(?:\s+|\s*,\s*)(-?[0-9]+(?:\.[0-9]+)?%?)(?:\s+|\s*,\s*)(-?[0-9]+(?:\.[0-9]+)?%?)(?:(?:\s+\s*(?:\/)\s*(0*(?:(?:0|[1-9][0-9]?)(?:\.[0-9]+)?|100(?:\.0+)?|\.[0-9]+)%|0*0*(?:\.[0-9]+)?|1(?:\.0+)?|\.[0-9]+))?\s*)?)\)/gi, 33 | /** Match CSS named and system colors. */ 34 | named: 35 | /(yellowgreen|yellow|whitesmoke|white|wheat|VisitedText|violet|turquoise|transparent|tomato|thistle|teal|tan|steelblue|springgreen|snow|slategrey|slategray|slateblue|skyblue|silver|sienna|SelectedItemText|SelectedItem|seashell|seagreen|sandybrown|salmon|saddlebrown|royalblue|rosybrown|red|rebeccapurple|purple|powderblue|plum|pink|peru|peachpuff|papayawhip|palevioletred|paleturquoise|palegreen|palegoldenrod|orchid|orangered|orange|olivedrab|olive|oldlace|navy|navajowhite|moccasin|mistyrose|mintcream|midnightblue|mediumvioletred|mediumturquoise|mediumspringgreen|mediumslateblue|mediumseagreen|mediumpurple|mediumorchid|mediumblue|mediumaquamarine|maroon|MarkText|Mark|magenta|LinkText|linen|limegreen|lime|lightyellow|lightsteelblue|lightslategrey|lightslategray|lightskyblue|lightseagreen|lightsalmon|lightpink|lightgrey|lightgreen|lightgray|lightgoldenrodyellow|lightcyan|lightcoral|lightblue|lemonchiffon|lawngreen|lavenderblush|lavender|khaki|ivory|indigo|indianred|hotpink|honeydew|HighlightText|Highlight|grey|greenyellow|green|GrayText|gray|goldenrod|gold|ghostwhite|gainsboro|fuchsia|forestgreen|floralwhite|firebrick|FieldText|Field|dodgerblue|dimgrey|dimgray|deepskyblue|deeppink|darkviolet|darkturquoise|darkslategrey|darkslategray|darkslateblue|darkseagreen|darksalmon|darkred|darkorchid|darkorange|darkolivegreen|darkmagenta|darkkhaki|darkgrey|darkgreen|darkgray|darkgoldenrod|darkcyan|darkblue|cyan|crimson|cornsilk|cornflowerblue|coral|chocolate|chartreuse|CanvasText|Canvas|cadetblue|ButtonText|ButtonFace|ButtonBorder|burlywood|brown|blueviolet|blue|blanchedalmond|black|bisque|beige|azure|aquamarine|aqua|antiquewhite|aliceblue|ActiveText|AccentColorText|AccentColor)/gi, 36 | }; 37 | -------------------------------------------------------------------------------- /packages/expressive-code-color-chips/src/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | type AnnotationBaseOptions, 3 | type AnnotationRenderOptions, 4 | definePlugin, 5 | ExpressiveCodeAnnotation, 6 | ExpressiveCodeLine, 7 | PluginStyleSettings, 8 | } from '@expressive-code/core'; 9 | import { h } from '@expressive-code/core/hast'; 10 | import { colors } from './colorRegEx'; 11 | 12 | const chipClass = 'ec-css-color-chip'; 13 | const localColorVariable = '--ec-css-color-chip'; 14 | /** Language tags where CSS color annotations should apply. */ 15 | const cssDialects = new Set(['css', 'scss', 'sass', 'less', 'stylus']); 16 | 17 | /** Adds an annotation to a CSS color, which will display that color as a chip next to it. */ 18 | class CssColorAnnotation extends ExpressiveCodeAnnotation { 19 | /** CSS color being annotated */ 20 | color: string; 21 | 22 | constructor(opts: AnnotationBaseOptions & { color: string }) { 23 | super(opts); 24 | this.color = opts.color; 25 | } 26 | 27 | render({ nodesToTransform }: AnnotationRenderOptions) { 28 | return nodesToTransform.map((node) => { 29 | return h(`span.${chipClass}`, { style: `${localColorVariable}: ${this.color}` }, node); 30 | }); 31 | } 32 | } 33 | 34 | /** Match all CSS comments in a line, including trailing unclosed comments or leading comments. */ 35 | const commentRegEx = /(?:^[^*]*\*\/)?\/\*[^*]*(?:\*\/)?/gi; 36 | 37 | /** 38 | * Process a code block line and annotate any colors found. 39 | */ 40 | function annotateLine(line: ExpressiveCodeLine) { 41 | /** An array of character positions that are inside comments. */ 42 | const commentPositions = [...line.text.matchAll(commentRegEx)] 43 | // Convert each match to an array of indexes for characters in that range. 44 | .flatMap((match) => Array.from(match[0]).map((_, i) => match.index + i)); 45 | [ 46 | // Colors expressed with explicit color syntax. 47 | ...line.text.matchAll(colors.programmatic), 48 | // Colors expressed with a named keyword, e.g. “blue” or “Canvas”. 49 | ...[...line.text.matchAll(colors.named)].filter( 50 | (match) => !commentPositions.includes(match.index) 51 | ), 52 | ] 53 | // Sort matches in reverse order by start position in the line (i.e. last match first). 54 | .sort((a, b) => b.index - a.index) 55 | // Annotate each match. 56 | .forEach((match) => { 57 | const color = match[0]; 58 | const columnStart = match.index; 59 | const columnEnd = columnStart + color.length; 60 | line.addAnnotation( 61 | new CssColorAnnotation({ 62 | color, 63 | inlineRange: { columnStart, columnEnd }, 64 | }) 65 | ); 66 | }); 67 | } 68 | 69 | /** 70 | * Expressive Code plugin that adds a small preview of each CSS color in your code examples. 71 | */ 72 | export function pluginColorChips() { 73 | return definePlugin({ 74 | name: 'ColorChips', 75 | hooks: { 76 | postprocessAnalyzedCode({ codeBlock }) { 77 | if (cssDialects.has(codeBlock.language)) { 78 | codeBlock.getLines().forEach((line) => annotateLine(line)); 79 | } 80 | }, 81 | }, 82 | styleSettings: new PluginStyleSettings({ 83 | defaultValues: { 84 | colorChips: { 85 | size: '1.2em', 86 | borderWidth: '1px', 87 | borderRadius: '50%', 88 | borderColor: ({ theme }) => theme.fg, 89 | transparencyShadeOne: ['#777', '#fff'], 90 | transparencyShadeTwo: ['#000', '#bbb'], 91 | }, 92 | }, 93 | }), 94 | baseStyles({ cssVar }) { 95 | const a = cssVar('colorChips.transparencyShadeOne'); 96 | const b = cssVar('colorChips.transparencyShadeTwo'); 97 | return ` 98 | .${chipClass}::before { 99 | content: ""; 100 | display: inline-block; 101 | box-sizing: border-box; 102 | width: ${cssVar('colorChips.size')}; 103 | height: ${cssVar('colorChips.size')}; 104 | margin-inline-end: 0.25em; 105 | vertical-align: text-bottom; 106 | background: 107 | linear-gradient(var(${localColorVariable}), var(${localColorVariable})), 108 | conic-gradient(${b} 25%, ${a} 25%, ${a} 50%, ${b} 50%, ${b} 75%, ${a} 75%); 109 | border-width: ${cssVar('colorChips.borderWidth')}; 110 | border-style: solid; 111 | border-color: ${cssVar('colorChips.borderColor')}; 112 | border-radius: ${cssVar('colorChips.borderRadius')}; 113 | } 114 | `; 115 | }, 116 | }); 117 | } 118 | 119 | interface ColorChipsStyleSettings { 120 | /** The size of each color chip. Default: `"1.2em"` */ 121 | size: string; 122 | /** The width of the border around each color chip. Default: `"1px"` */ 123 | borderWidth: string; 124 | /** The roundness of each color chip. Default: `"50%"` */ 125 | borderRadius: string; 126 | /** The color of the border around each chip. Default: `({ theme }) => theme.fg` */ 127 | borderColor: string; 128 | /** The first of two shades used in the checkerboard background for transparent colors. Default: `["#777", "#fff"]` */ 129 | transparencyShadeOne: string; 130 | /** The second of two shades used in the checkerboard background for transparent colors. Default: `["#000", "#bbb"]` */ 131 | transparencyShadeTwo: string; 132 | } 133 | 134 | declare module '@expressive-code/core' { 135 | export interface StyleSettings { 136 | /** Style overrides for the CSS color chips plugin. */ 137 | colorChips: ColorChipsStyleSettings; 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /packages/expressive-code-color-chips/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src/**/*.ts"], 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "declaration": true, 6 | "declarationMap": true, 7 | "emitDeclarationOnly": true, 8 | "esModuleInterop": true, 9 | "exactOptionalPropertyTypes": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "resolveJsonModule": true, 12 | "skipLibCheck": true, 13 | "strict": true, 14 | "moduleResolution": "node", 15 | "module": "ESNext", 16 | "target": "ESNext", 17 | "outDir": "./dist" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - 'packages/**/*' 3 | - 'docs' 4 | 5 | --------------------------------------------------------------------------------