diff --git a/docs/src/content/docs/guides/authoring-content.md b/docs/src/content/docs/guides/authoring-content.md index 2fe848151a0..fa8330eac23 100644 --- a/docs/src/content/docs/guides/authoring-content.md +++ b/docs/src/content/docs/guides/authoring-content.md @@ -151,6 +151,20 @@ Astro helps you build faster websites with [“Islands Architecture”](https:// ::: ``` +### Custom aside icons + +You can specify a custom icon for the aside in curly brackets following the aside type/title, e.g. `:::tip[Did you know?]{icon="heart"}`. + +:::tip[Did you know?]{icon="heart"} +Astro helps you build faster websites with [“Islands Architecture”](https://docs.astro.build/en/concepts/islands/). +::: + +```md +:::tip[Did you know?]{icon="heart"} +Astro helps you build faster websites with [“Islands Architecture”](https://docs.astro.build/en/concepts/islands/). +::: +``` + ### More aside types Caution and danger asides are helpful for drawing a user’s attention to details that may trip them up. diff --git a/packages/starlight/__tests__/remark-rehype/asides.test.ts b/packages/starlight/__tests__/remark-rehype/asides.test.ts index 327766e4594..c829c8e56ec 100644 --- a/packages/starlight/__tests__/remark-rehype/asides.test.ts +++ b/packages/starlight/__tests__/remark-rehype/asides.test.ts @@ -103,6 +103,29 @@ Some text ); }); +describe('custom icons', () => { + test.each(['note', 'tip', 'caution', 'danger'])('%s with custom label', async (type) => { + const res = await processor.render(` +:::${type}{icon="heart"} +Some text +::: + `); + expect(res.code).includes( + 'M20.16 5A6.29 6.29 0 0 0 12 4.36a6.27 6.27 0 0 0-8.16 9.48l6.21 6.22a2.78 2.78 0 0 0 3.9 0l6.21-6.22a6.27 6.27 0 0 0 0-8.84m-1.41 7.46-6.21 6.21a.76.76 0 0 1-1.08 0l-6.21-6.24a4.29 4.29 0 0 1 0-6 4.27 4.27 0 0 1 6 0 1 1 0 0 0 1.42 0 4.27 4.27 0 0 1 6 0 4.29 4.29 0 0 1 .08 6Z' + ); + + expect(async () => + processor.render(` +:::${type}{icon="invalid-icon-name"} +Some text +::: + `) + ).rejects.toThrowError( + 'Failed to parse Markdown file "undefined":\nIcon name should be part of Starlight\'s icon list.' + ); + }); +}); + test('ignores unknown directive variants', async () => { const res = await processor.render(` :::unknown diff --git a/packages/starlight/integrations/asides.ts b/packages/starlight/integrations/asides.ts index f98cc090826..f7b023064da 100644 --- a/packages/starlight/integrations/asides.ts +++ b/packages/starlight/integrations/asides.ts @@ -17,6 +17,10 @@ import { visit } from 'unist-util-visit'; import type { StarlightConfig } from '../types'; import type { createTranslationSystemFromFs } from '../utils/translations-fs'; import { pathToLocale } from './shared/pathToLocale'; +import { Icons } from '../components/Icons'; +import { fromHtml } from 'hast-util-from-html'; +import type { Element } from 'hast'; +import { AstroError } from 'astro/errors'; interface AsidesOptions { starlightConfig: { locales: StarlightConfig['locales'] }; @@ -150,6 +154,7 @@ function remarkAsides(options: AsidesOptions): Plugin<[], Root> { return; } const variant = node.name; + const attributes = node.attributes; if (!isAsideVariant(variant)) return; // remark-directive converts a container’s “label” to a paragraph added as the head of its @@ -171,6 +176,19 @@ function remarkAsides(options: AsidesOptions): Plugin<[], Root> { node.children.splice(0, 1); } + let iconPath = iconPaths[variant]; + if (attributes) { + if (attributes['icon']) { + const iconName = attributes['icon'] as keyof typeof Icons; + if (!Object.keys(Icons).includes(iconName)) { + throw new AstroError("Icon name should be part of Starlight's icon list."); + } + const { properties } = fromHtml(Icons[iconName], { fragment: true }) + .children[0] as Element; + iconPath = [s('path', properties)]; + } + } + const aside = h( 'aside', { @@ -188,7 +206,7 @@ function remarkAsides(options: AsidesOptions): Plugin<[], Root> { fill: 'currentColor', class: 'starlight-aside__icon', }, - iconPaths[variant] + iconPath ), ...titleNode, ]),