summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorShubham Padia2025-07-16 20:33:15 +0700
committerGitHub2025-07-16 15:33:15 +0200
commit778b743cdb832551ed576c745728358d8bbf9d7a (patch)
treea3e9d3c6f51c86b43a53ed7dd1f69e36497357d8
parent3917b206da26522f73bbe0c1120de9acae5972c5 (diff)
downloadIT.starlight-778b743cdb832551ed576c745728358d8bbf9d7a.tar.gz
IT.starlight-778b743cdb832551ed576c745728358d8bbf9d7a.tar.bz2
IT.starlight-778b743cdb832551ed576c745728358d8bbf9d7a.zip
Aside: Support custom icons (#2261)
Co-authored-by: HiDeoo <494699+HiDeoo@users.noreply.github.com> Co-authored-by: Chris Swithinbank <swithinbank@gmail.com>
-rw-r--r--.changeset/brave-apples-give.md5
-rw-r--r--.changeset/five-flowers-flash.md5
-rw-r--r--docs/src/content/docs/components/asides.mdx36
-rw-r--r--docs/src/content/docs/guides/authoring-content.mdx15
-rw-r--r--packages/markdoc/index.mjs4
-rw-r--r--packages/markdoc/package.json2
-rw-r--r--packages/starlight/__tests__/remark-rehype/asides.test.ts76
-rw-r--r--packages/starlight/__tests__/remark-rehype/snapshots/generates-aside-caution-custom-icon.html1
-rw-r--r--packages/starlight/__tests__/remark-rehype/snapshots/generates-aside-caution-custom-label-and-icon.html1
-rw-r--r--packages/starlight/__tests__/remark-rehype/snapshots/generates-aside-danger-custom-icon.html1
-rw-r--r--packages/starlight/__tests__/remark-rehype/snapshots/generates-aside-danger-custom-label-and-icon.html1
-rw-r--r--packages/starlight/__tests__/remark-rehype/snapshots/generates-aside-note-custom-icon.html1
-rw-r--r--packages/starlight/__tests__/remark-rehype/snapshots/generates-aside-note-custom-label-and-icon.html1
-rw-r--r--packages/starlight/__tests__/remark-rehype/snapshots/generates-aside-note-multiple-path-custom-icon.html1
-rw-r--r--packages/starlight/__tests__/remark-rehype/snapshots/generates-aside-tip-custom-icon.html1
-rw-r--r--packages/starlight/__tests__/remark-rehype/snapshots/generates-aside-tip-custom-label-and-icon.html1
-rw-r--r--packages/starlight/integrations/asides.ts46
-rw-r--r--packages/starlight/user-components/Aside.astro9
18 files changed, 200 insertions, 7 deletions
diff --git a/.changeset/brave-apples-give.md b/.changeset/brave-apples-give.md
new file mode 100644
index 00000000..4963ac42
--- /dev/null
+++ b/.changeset/brave-apples-give.md
@@ -0,0 +1,5 @@
+---
+'@astrojs/starlight-markdoc': minor
+---
+
+Adds support for the `icon` attribute in the `aside` tag, allowing the use of any of Starlight’s built-in icons.
diff --git a/.changeset/five-flowers-flash.md b/.changeset/five-flowers-flash.md
new file mode 100644
index 00000000..6e5763bb
--- /dev/null
+++ b/.changeset/five-flowers-flash.md
@@ -0,0 +1,5 @@
+---
+'@astrojs/starlight': minor
+---
+
+Adds support for using any of Starlight’s built-in icons in asides.
diff --git a/docs/src/content/docs/components/asides.mdx b/docs/src/content/docs/components/asides.mdx
index a28375b0..e3c0ac8c 100644
--- a/docs/src/content/docs/components/asides.mdx
+++ b/docs/src/content/docs/components/asides.mdx
@@ -132,6 +132,36 @@ A warning aside *with* a custom title.
</Preview>
+### Use custom icons
+
+Override the default aside icons by using the [`icon`](#icon) attribute set to the name of [one of Starlight’s built-in icons](/reference/icons/#all-icons).
+
+<Preview>
+
+```mdx 'icon="starlight"'
+import { Aside } from '@astrojs/starlight/components';
+
+<Aside type="tip" icon="starlight">
+ A warning aside *with* a custom icon.
+</Aside>
+```
+
+<Fragment slot="markdoc">
+
+```markdoc 'icon="starlight"'
+{% aside type="tip" icon="starlight" %}
+A warning aside *with* a custom icon.
+{% /aside %}
+```
+
+</Fragment>
+
+<Aside slot="preview" type="tip" icon="starlight">
+ A warning aside *with* a custom icon.
+</Aside>
+
+</Preview>
+
## `<Aside>` Props
**Implementation:** [`Aside.astro`](https://github.com/withastro/starlight/blob/main/packages/starlight/user-components/Aside.astro)
@@ -156,3 +186,9 @@ The type of aside to display:
The title of the aside to display.
If `title` is not set, the default title for the current aside `type` will be used.
+
+### `icon`
+
+**type:** [`StarlightIcon`](/reference/icons/#starlighticon-type)
+
+An aside can include an `icon` attribute set to the name of [one of Starlight’s built-in icons](/reference/icons/#all-icons).
diff --git a/docs/src/content/docs/guides/authoring-content.mdx b/docs/src/content/docs/guides/authoring-content.mdx
index be27ca80..9c6a2ad7 100644
--- a/docs/src/content/docs/guides/authoring-content.mdx
+++ b/docs/src/content/docs/guides/authoring-content.mdx
@@ -151,6 +151,21 @@ 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 or [custom title](#custom-aside-titles), e.g. `:::tip{icon="heart"}` or `:::tip[Did you know?]{icon="heart"}` respectively.
+The icon name must be set to the name of [one of Starlight’s built-in icons](/reference/icons/#all-icons).
+
+:::tip{icon="heart"}
+Astro helps you build faster websites with [“Islands Architecture”](https://docs.astro.build/en/concepts/islands/).
+:::
+
+```md
+:::tip{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/markdoc/index.mjs b/packages/markdoc/index.mjs
index 82a472f4..32f19ecf 100644
--- a/packages/markdoc/index.mjs
+++ b/packages/markdoc/index.mjs
@@ -67,6 +67,10 @@ export const StarlightMarkdocPreset = {
aside: {
render: component('@astrojs/starlight/components', 'Aside'),
attributes: {
+ icon: {
+ type: String,
+ required: false,
+ },
title: {
type: String,
required: false,
diff --git a/packages/markdoc/package.json b/packages/markdoc/package.json
index 1d60681d..e949671f 100644
--- a/packages/markdoc/package.json
+++ b/packages/markdoc/package.json
@@ -23,7 +23,7 @@
},
"peerDependencies": {
"@astrojs/markdoc": ">=0.12.1",
- "@astrojs/starlight": ">=0.34.0"
+ "@astrojs/starlight": ">=0.35.0"
},
"publishConfig": {
"provenance": true
diff --git a/packages/starlight/__tests__/remark-rehype/asides.test.ts b/packages/starlight/__tests__/remark-rehype/asides.test.ts
index 35ad73fb..04ab62cf 100644
--- a/packages/starlight/__tests__/remark-rehype/asides.test.ts
+++ b/packages/starlight/__tests__/remark-rehype/asides.test.ts
@@ -1,7 +1,7 @@
import { createMarkdownProcessor, type MarkdownProcessor } from '@astrojs/markdown-remark';
import type { Root } from 'mdast';
import { visit } from 'unist-util-visit';
-import { describe, expect, test } from 'vitest';
+import { describe, expect, test, vi } from 'vitest';
import { starlightAsides, remarkDirectivesRestoration } from '../../integrations/asides';
import { createTranslationSystemFromFs } from '../../utils/translations-fs';
import { StarlightConfigSchema, type StarlightUserConfig } from '../../utils/user-config';
@@ -126,6 +126,80 @@ Some text
);
});
+describe('custom icons', () => {
+ test.each(['note', 'tip', 'caution', 'danger'])('%s with custom icon', async (type) => {
+ const res = await renderMarkdown(`
+:::${type}{icon="heart"}
+Some text
+:::
+ `);
+ await expect(res.code).toMatchFileSnapshot(
+ `./snapshots/generates-aside-${type}-custom-icon.html`
+ );
+ });
+
+ test.each(['note', 'tip', 'caution', 'danger'])('%s with invalid custom icon', async (type) => {
+ // Temporarily mock console.error to avoid cluttering test output when the Astro Markdown
+ // processor logs an error before rethrowing it.
+ // https://github.com/withastro/astro/blob/98853ce7e31a8002fd7be83d7932a53cfec84d27/packages/markdown/remark/src/index.ts#L161
+ const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {});
+
+ await expect(async () =>
+ renderMarkdown(
+ `
+:::${type}{icon="invalid-icon-name"}
+Some text
+:::
+ `
+ )
+ ).rejects.toThrowError(
+ // We are not relying on `toThrowErrorMatchingInlineSnapshot()` and our custom snapshot
+ // serializer in this specific test as error thrown in a remark plugin includes a dynamic file
+ // path.
+ expect.objectContaining({
+ type: 'AstroUserError',
+ hint: expect.stringMatching(
+ /An aside custom icon must be set to the name of one of Starlight’s built-in icons, but received `invalid-icon-name`/
+ ),
+ })
+ );
+
+ // Restore the original console.error implementation.
+ consoleError.mockRestore();
+ });
+
+ test('test custom icon with multiple paths inside the svg', async () => {
+ const res = await renderMarkdown(`
+:::note{icon="external"}
+Some text
+:::
+ `);
+ await expect(res.code).toMatchFileSnapshot(
+ `./snapshots/generates-aside-note-multiple-path-custom-icon.html`
+ );
+ const pathCount = (res.code.match(/path/g) || []).length;
+ // If we have two pairs of opening and closing tags of path,
+ // we will have 4 occurences of that word.
+ expect(pathCount).eq(4);
+ });
+});
+
+describe('custom labels with custom icons', () => {
+ test.each(['note', 'tip', 'caution', 'danger'])('%s with custom label', async (type) => {
+ const label = 'Custom Label';
+ const res = await renderMarkdown(`
+:::${type}[${label}]{icon="heart"}
+Some text
+:::
+ `);
+ expect(res.code).includes(`aria-label="${label}"`);
+ expect(res.code).includes(`</svg>${label}</p>`);
+ await expect(res.code).toMatchFileSnapshot(
+ `./snapshots/generates-aside-${type}-custom-label-and-icon.html`
+ );
+ });
+});
+
test('ignores unknown directive variants', async () => {
const res = await renderMarkdown(`
:::unknown
diff --git a/packages/starlight/__tests__/remark-rehype/snapshots/generates-aside-caution-custom-icon.html b/packages/starlight/__tests__/remark-rehype/snapshots/generates-aside-caution-custom-icon.html
new file mode 100644
index 00000000..564ad6cd
--- /dev/null
+++ b/packages/starlight/__tests__/remark-rehype/snapshots/generates-aside-caution-custom-icon.html
@@ -0,0 +1 @@
+<aside aria-label="Caution" class="starlight-aside starlight-aside--caution"><p class="starlight-aside__title" aria-hidden="true"><svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor" class="starlight-aside__icon"><path d="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"></path></svg>Caution</p><div class="starlight-aside__content"><p>Some text</p></div></aside> \ No newline at end of file
diff --git a/packages/starlight/__tests__/remark-rehype/snapshots/generates-aside-caution-custom-label-and-icon.html b/packages/starlight/__tests__/remark-rehype/snapshots/generates-aside-caution-custom-label-and-icon.html
new file mode 100644
index 00000000..c6075535
--- /dev/null
+++ b/packages/starlight/__tests__/remark-rehype/snapshots/generates-aside-caution-custom-label-and-icon.html
@@ -0,0 +1 @@
+<aside aria-label="Custom Label" class="starlight-aside starlight-aside--caution"><p class="starlight-aside__title" aria-hidden="true"><svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor" class="starlight-aside__icon"><path d="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"></path></svg>Custom Label</p><div class="starlight-aside__content"><p>Some text</p></div></aside> \ No newline at end of file
diff --git a/packages/starlight/__tests__/remark-rehype/snapshots/generates-aside-danger-custom-icon.html b/packages/starlight/__tests__/remark-rehype/snapshots/generates-aside-danger-custom-icon.html
new file mode 100644
index 00000000..c276e379
--- /dev/null
+++ b/packages/starlight/__tests__/remark-rehype/snapshots/generates-aside-danger-custom-icon.html
@@ -0,0 +1 @@
+<aside aria-label="Danger" class="starlight-aside starlight-aside--danger"><p class="starlight-aside__title" aria-hidden="true"><svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor" class="starlight-aside__icon"><path d="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"></path></svg>Danger</p><div class="starlight-aside__content"><p>Some text</p></div></aside> \ No newline at end of file
diff --git a/packages/starlight/__tests__/remark-rehype/snapshots/generates-aside-danger-custom-label-and-icon.html b/packages/starlight/__tests__/remark-rehype/snapshots/generates-aside-danger-custom-label-and-icon.html
new file mode 100644
index 00000000..fc8c46ae
--- /dev/null
+++ b/packages/starlight/__tests__/remark-rehype/snapshots/generates-aside-danger-custom-label-and-icon.html
@@ -0,0 +1 @@
+<aside aria-label="Custom Label" class="starlight-aside starlight-aside--danger"><p class="starlight-aside__title" aria-hidden="true"><svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor" class="starlight-aside__icon"><path d="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"></path></svg>Custom Label</p><div class="starlight-aside__content"><p>Some text</p></div></aside> \ No newline at end of file
diff --git a/packages/starlight/__tests__/remark-rehype/snapshots/generates-aside-note-custom-icon.html b/packages/starlight/__tests__/remark-rehype/snapshots/generates-aside-note-custom-icon.html
new file mode 100644
index 00000000..5727e7be
--- /dev/null
+++ b/packages/starlight/__tests__/remark-rehype/snapshots/generates-aside-note-custom-icon.html
@@ -0,0 +1 @@
+<aside aria-label="Note" class="starlight-aside starlight-aside--note"><p class="starlight-aside__title" aria-hidden="true"><svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor" class="starlight-aside__icon"><path d="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"></path></svg>Note</p><div class="starlight-aside__content"><p>Some text</p></div></aside> \ No newline at end of file
diff --git a/packages/starlight/__tests__/remark-rehype/snapshots/generates-aside-note-custom-label-and-icon.html b/packages/starlight/__tests__/remark-rehype/snapshots/generates-aside-note-custom-label-and-icon.html
new file mode 100644
index 00000000..506831fc
--- /dev/null
+++ b/packages/starlight/__tests__/remark-rehype/snapshots/generates-aside-note-custom-label-and-icon.html
@@ -0,0 +1 @@
+<aside aria-label="Custom Label" class="starlight-aside starlight-aside--note"><p class="starlight-aside__title" aria-hidden="true"><svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor" class="starlight-aside__icon"><path d="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"></path></svg>Custom Label</p><div class="starlight-aside__content"><p>Some text</p></div></aside> \ No newline at end of file
diff --git a/packages/starlight/__tests__/remark-rehype/snapshots/generates-aside-note-multiple-path-custom-icon.html b/packages/starlight/__tests__/remark-rehype/snapshots/generates-aside-note-multiple-path-custom-icon.html
new file mode 100644
index 00000000..35cd1b77
--- /dev/null
+++ b/packages/starlight/__tests__/remark-rehype/snapshots/generates-aside-note-multiple-path-custom-icon.html
@@ -0,0 +1 @@
+<aside aria-label="Note" class="starlight-aside starlight-aside--note"><p class="starlight-aside__title" aria-hidden="true"><svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor" class="starlight-aside__icon"><path d="M19.33 10.18a1 1 0 0 1-.77 0 1 1 0 0 1-.62-.93l.01-1.83-8.2 8.2a1 1 0 0 1-1.41-1.42l8.2-8.2H14.7a1 1 0 0 1 0-2h4.25a1 1 0 0 1 1 1v4.25a1 1 0 0 1-.62.93Z"></path><path d="M11 4a1 1 0 1 1 0 2H7a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1v-4a1 1 0 1 1 2 0v4a3 3 0 0 1-3 3H7a3 3 0 0 1-3-3V7a3 3 0 0 1 3-3h4Z"></path></svg>Note</p><div class="starlight-aside__content"><p>Some text</p></div></aside> \ No newline at end of file
diff --git a/packages/starlight/__tests__/remark-rehype/snapshots/generates-aside-tip-custom-icon.html b/packages/starlight/__tests__/remark-rehype/snapshots/generates-aside-tip-custom-icon.html
new file mode 100644
index 00000000..6f6e1eb9
--- /dev/null
+++ b/packages/starlight/__tests__/remark-rehype/snapshots/generates-aside-tip-custom-icon.html
@@ -0,0 +1 @@
+<aside aria-label="Tip" class="starlight-aside starlight-aside--tip"><p class="starlight-aside__title" aria-hidden="true"><svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor" class="starlight-aside__icon"><path d="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"></path></svg>Tip</p><div class="starlight-aside__content"><p>Some text</p></div></aside> \ No newline at end of file
diff --git a/packages/starlight/__tests__/remark-rehype/snapshots/generates-aside-tip-custom-label-and-icon.html b/packages/starlight/__tests__/remark-rehype/snapshots/generates-aside-tip-custom-label-and-icon.html
new file mode 100644
index 00000000..b263140d
--- /dev/null
+++ b/packages/starlight/__tests__/remark-rehype/snapshots/generates-aside-tip-custom-label-and-icon.html
@@ -0,0 +1 @@
+<aside aria-label="Custom Label" class="starlight-aside starlight-aside--tip"><p class="starlight-aside__title" aria-hidden="true"><svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor" class="starlight-aside__icon"><path d="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"></path></svg>Custom Label</p><div class="starlight-aside__content"><p>Some text</p></div></aside> \ No newline at end of file
diff --git a/packages/starlight/integrations/asides.ts b/packages/starlight/integrations/asides.ts
index 85334d70..42e6098d 100644
--- a/packages/starlight/integrations/asides.ts
+++ b/packages/starlight/integrations/asides.ts
@@ -1,7 +1,7 @@
/// <reference types="mdast-util-directive" />
import type { AstroConfig, AstroIntegration, AstroUserConfig } from 'astro';
-import { h as _h, s as _s, type Properties } from 'hastscript';
+import { h as _h, s as _s, type Properties, type Result } from 'hastscript';
import type { Node, Paragraph as P, Parent, PhrasingContent, Root } from 'mdast';
import {
type Directives,
@@ -14,8 +14,12 @@ import { toString } from 'mdast-util-to-string';
import remarkDirective from 'remark-directive';
import type { Plugin, Transformer } from 'unified';
import { visit } from 'unist-util-visit';
-import type { HookParameters, StarlightConfig } from '../types';
+import type { HookParameters, StarlightConfig, StarlightIcon } from '../types';
import { getRemarkRehypeDocsCollectionPath, shouldTransformFile } from './remark-rehype-utils';
+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: Pick<StarlightConfig, 'defaultLocale' | 'locales'>;
@@ -88,6 +92,20 @@ function transformUnhandledDirective(
}
}
+/** Hacky function that generates the children of an mdast SVG tree. */
+function makeSvgChildNodes(children: Result['children']): any[] {
+ const nodes: P[] = [];
+ for (const child of children) {
+ if (child.type !== 'element') continue;
+ nodes.push({
+ type: 'paragraph',
+ data: { hName: child.tagName, hProperties: child.properties },
+ children: makeSvgChildNodes(child.children),
+ });
+ }
+ return nodes;
+}
+
/**
* remark plugin that converts blocks delimited with `:::` into styled
* asides (a.k.a. “callouts”, “admonitions”, etc.). Depends on the
@@ -164,6 +182,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
@@ -185,6 +204,19 @@ function remarkAsides(options: AsidesOptions): Plugin<[], Root> {
node.children.splice(0, 1);
}
+ let iconPath = iconPaths[variant];
+
+ if (attributes?.['icon']) {
+ const iconName = attributes['icon'] as StarlightIcon;
+ const icon = Icons[iconName];
+ if (!icon) throwInvalidAsideIconError(iconName);
+ // Omit the root node and return only the first child which is the SVG element.
+ const iconHastTree = fromHtml(`<svg>${icon}</svg>`, { fragment: true, space: 'svg' })
+ .children[0] as Element;
+ // Render all SVG child nodes.
+ iconPath = makeSvgChildNodes(iconHastTree.children);
+ }
+
const aside = h(
'aside',
{
@@ -202,7 +234,7 @@ function remarkAsides(options: AsidesOptions): Plugin<[], Root> {
fill: 'currentColor',
class: 'starlight-aside__icon',
},
- iconPaths[variant]
+ iconPath
),
...titleNode,
]),
@@ -221,6 +253,14 @@ function remarkAsides(options: AsidesOptions): Plugin<[], Root> {
type RemarkPlugins = NonNullable<NonNullable<AstroUserConfig['markdown']>['remarkPlugins']>;
+export function throwInvalidAsideIconError(icon: string) {
+ throw new AstroError(
+ 'Invalid aside icon',
+ `An aside custom icon must be set to the name of one of Starlight\’s built-in icons, but received \`${icon}\`.\n\n` +
+ 'See https://starlight.astro.build/reference/icons/#all-icons for a list of available icons.'
+ );
+}
+
export function starlightAsides(options: AsidesOptions): RemarkPlugins {
return [remarkDirective, remarkAsides(options)];
}
diff --git a/packages/starlight/user-components/Aside.astro b/packages/starlight/user-components/Aside.astro
index 785a4f20..1a1ab00d 100644
--- a/packages/starlight/user-components/Aside.astro
+++ b/packages/starlight/user-components/Aside.astro
@@ -1,6 +1,8 @@
---
import { AstroError } from 'astro/errors';
import Icon from './Icon.astro';
+import { Icons, type StarlightIcon } from '../components/Icons';
+import { throwInvalidAsideIconError } from '../integrations/asides';
const asideVariants = ['note', 'tip', 'caution', 'danger'] as const;
const icons = { note: 'information', tip: 'rocket', caution: 'warning', danger: 'error' } as const;
@@ -8,9 +10,10 @@ const icons = { note: 'information', tip: 'rocket', caution: 'warning', danger:
interface Props {
type?: (typeof asideVariants)[number];
title?: string;
+ icon?: StarlightIcon;
}
-let { type = 'note', title } = Astro.props;
+let { type = 'note', title, icon } = Astro.props;
if (!asideVariants.includes(type)) {
throw new AstroError(
@@ -20,6 +23,8 @@ if (!asideVariants.includes(type)) {
);
}
+if (icon && !Icons[icon]) throwInvalidAsideIconError(icon);
+
if (!title) {
title = Astro.locals.t(`aside.${type}`);
}
@@ -27,7 +32,7 @@ if (!title) {
<aside aria-label={title} class={`starlight-aside starlight-aside--${type}`}>
<p class="starlight-aside__title" aria-hidden="true">
- <Icon name={icons[type]} class="starlight-aside__icon" />{title}
+ <Icon name={icon || icons[type]} class="starlight-aside__icon" />{title}
</p>
<div class="starlight-aside__content">
<slot />