diff --git a/docs/src/content/docs/components/asides.mdx b/docs/src/content/docs/components/asides.mdx index a28375b0d60..9ccf1ff189c 100644 --- a/docs/src/content/docs/components/asides.mdx +++ b/docs/src/content/docs/components/asides.mdx @@ -49,6 +49,10 @@ Other content is also supported in asides. + + ```` @@ -73,6 +77,10 @@ Other content is also supported in asides. {% aside type="danger" %} Do not give your password to anyone. {% /aside %} + +{% aside icon="heart" %} +Aside with a custom icon. The icon must be part of starlight's icon list. +{% /aside %} ```` @@ -95,6 +103,10 @@ Do not give your password to anyone. + + diff --git a/docs/src/content/docs/guides/authoring-content.mdx b/docs/src/content/docs/guides/authoring-content.mdx index 819e4f50016..98fccc69d10 100644 --- a/docs/src/content/docs/guides/authoring-content.mdx +++ b/docs/src/content/docs/guides/authoring-content.mdx @@ -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 cda21bb7b5f..d72914a81bf 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 00ca11a603c..bbe1c9b03fb 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'] }; @@ -160,6 +164,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 @@ -181,6 +186,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', { @@ -198,7 +216,7 @@ function remarkAsides(options: AsidesOptions): Plugin<[], Root> { fill: 'currentColor', class: 'starlight-aside__icon', }, - iconPaths[variant] + iconPath ), ...titleNode, ]), diff --git a/packages/starlight/user-components/Aside.astro b/packages/starlight/user-components/Aside.astro index 471f2fa0ae1..cdb448d511f 100644 --- a/packages/starlight/user-components/Aside.astro +++ b/packages/starlight/user-components/Aside.astro @@ -12,7 +12,7 @@ interface Props { title?: string; } -let { type = 'note', title } = Astro.props; +let { type = 'note', title, icon } = Astro.props; if (!asideVariants.includes(type)) { throw new AstroError( @@ -30,7 +30,7 @@ if (!title) {