summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHiDeoo2023-10-06 18:28:35 +0200
committerGitHub2023-10-06 18:28:35 +0200
commit903a57942ceb99b68672c3fa54622b39cc5d76f8 (patch)
tree84923d39e7b0cd523a9edd7f1440f36ba135b7c7
parentb7b23a2c90a25fe8ea08338379b83d19c74d9037 (diff)
downloadIT.starlight-903a57942ceb99b68672c3fa54622b39cc5d76f8.tar.gz
IT.starlight-903a57942ceb99b68672c3fa54622b39cc5d76f8.tar.bz2
IT.starlight-903a57942ceb99b68672c3fa54622b39cc5d76f8.zip
Add support for custom HTML attributes to sidebar links (#774)
Co-authored-by: Chris Swithinbank <swithinbank@gmail.com>
-rw-r--r--.changeset/fuzzy-foxes-confess.md5
-rw-r--r--docs/src/components/sidebar-preview.astro5
-rw-r--r--docs/src/content/docs/guides/sidebar.mdx44
-rw-r--r--docs/src/content/docs/reference/configuration.md1
-rw-r--r--docs/src/content/docs/reference/frontmatter.md30
-rw-r--r--packages/starlight/__tests__/basics/navigation-labels.test.ts4
-rw-r--r--packages/starlight/__tests__/basics/navigation-order.test.ts4
-rw-r--r--packages/starlight/__tests__/basics/navigation.test.ts11
-rw-r--r--packages/starlight/__tests__/i18n/navigation-order.test.ts10
-rw-r--r--packages/starlight/__tests__/sidebar/navigation-attributes.test.ts93
-rw-r--r--packages/starlight/__tests__/sidebar/navigation-badges.test.ts16
-rw-r--r--packages/starlight/__tests__/sidebar/navigation-hidden.test.ts15
-rw-r--r--packages/starlight/__tests__/sidebar/navigation-order.test.ts16
-rw-r--r--packages/starlight/__tests__/sidebar/navigation.test.ts16
-rw-r--r--packages/starlight/__tests__/sidebar/vitest.config.ts5
-rw-r--r--packages/starlight/components/SidebarSublist.astro3
-rw-r--r--packages/starlight/schema.ts3
-rw-r--r--packages/starlight/schemas/sidebar.ts89
-rw-r--r--packages/starlight/utils/navigation.ts25
-rw-r--r--packages/starlight/utils/user-config.ts75
-rw-r--r--packages/starlight/vitest.config.ts8
21 files changed, 390 insertions, 88 deletions
diff --git a/.changeset/fuzzy-foxes-confess.md b/.changeset/fuzzy-foxes-confess.md
new file mode 100644
index 00000000..6dbec2dc
--- /dev/null
+++ b/.changeset/fuzzy-foxes-confess.md
@@ -0,0 +1,5 @@
+---
+'@astrojs/starlight': minor
+---
+
+Support adding HTML attributes to sidebar links from config and frontmatter
diff --git a/docs/src/components/sidebar-preview.astro b/docs/src/components/sidebar-preview.astro
index 0865d2f6..732e766e 100644
--- a/docs/src/components/sidebar-preview.astro
+++ b/docs/src/components/sidebar-preview.astro
@@ -1,5 +1,5 @@
---
-import type { AutoSidebarGroup, SidebarItem } from '../../../packages/starlight/utils/user-config';
+import type { AutoSidebarGroup, SidebarItem } from '../../../packages/starlight/schemas/sidebar';
import SidebarSublist from '../../../packages/starlight/components/SidebarSublist.astro';
import type { SidebarEntry } from '../../../packages/starlight/utils/navigation';
@@ -20,11 +20,10 @@ function makeEntries(items: SidebarConfig): SidebarEntry[] {
href: item.link,
isCurrent: false,
badge: item.badge,
+ attrs: item.attrs ?? {},
};
}
- item;
-
return {
type: 'group',
label: item.label,
diff --git a/docs/src/content/docs/guides/sidebar.mdx b/docs/src/content/docs/guides/sidebar.mdx
index 5f775c7a..6a732e6d 100644
--- a/docs/src/content/docs/guides/sidebar.mdx
+++ b/docs/src/content/docs/guides/sidebar.mdx
@@ -316,6 +316,50 @@ The configuration above generates the following sidebar:
]}
/>
+## Custom HTML attributes
+
+Links can also include an `attrs` property to add custom HTML attributes to the link element.
+
+In the following example, `attrs` is used to add a `target="_blank"` attribute, so that the link opens in a new tab, and to apply a custom `style` attribute to italicize the link label:
+
+```js
+starlight({
+ sidebar: [
+ {
+ label: 'Guides',
+ items: [
+ // An external link to the Astro docs opening in a new tab.
+ {
+ label: 'Astro Docs',
+ link: 'https://docs.astro.build/',
+ attrs: { target: '_blank', style: 'font-style: italic' },
+ },
+ ],
+ },
+ ],
+});
+```
+
+The configuration above generates the following sidebar:
+
+<SidebarPreview
+ config={[
+ {
+ label: 'Guides',
+ items: [
+ {
+ label: 'Astro Docs',
+ link: 'https://docs.astro.build/',
+ attrs: {
+ target: '_blank',
+ style: 'font-style: italic',
+ },
+ },
+ ],
+ },
+ ]}
+/>
+
## Internationalization
Use the `translations` property on link and group entries to translate the link or group label for each supported language.
diff --git a/docs/src/content/docs/reference/configuration.md b/docs/src/content/docs/reference/configuration.md
index 9d742e46..ad2d8591 100644
--- a/docs/src/content/docs/reference/configuration.md
+++ b/docs/src/content/docs/reference/configuration.md
@@ -187,6 +187,7 @@ type SidebarItem = {
| {
link: string;
badge?: string | BadgeConfig;
+ attrs?: Record<string, string | number | boolean | undefined>;
}
| { items: SidebarItem[]; collapsed?: boolean }
| {
diff --git a/docs/src/content/docs/reference/frontmatter.md b/docs/src/content/docs/reference/frontmatter.md
index 7b305411..9b401383 100644
--- a/docs/src/content/docs/reference/frontmatter.md
+++ b/docs/src/content/docs/reference/frontmatter.md
@@ -224,10 +224,22 @@ pagefind: false
### `sidebar`
-**type:** `{ label?: string; order?: number; hidden?: boolean; badge?: string | BadgeConfig }`
+**type:** [`SidebarConfig`](#sidebarconfig)
Control how this page is displayed in the [sidebar](/reference/configuration/#sidebar), when using an autogenerated link group.
+#### `SidebarConfig`
+
+```ts
+interface SidebarConfig {
+ label?: string;
+ order?: number;
+ hidden?: boolean;
+ badge?: string | BadgeConfig;
+ attrs?: Record<string, string | number | boolean | undefined>;
+}
+```
+
#### `label`
**type:** `string`
@@ -299,3 +311,19 @@ sidebar:
variant: caution
---
```
+
+#### `attrs`
+
+**type:** `Record<string, string | number | boolean | undefined>`
+
+HTML attributes to add to the page link in the sidebar when displayed in an autogenerated group of links.
+
+```md
+---
+title: Page opening in a new tab
+sidebar:
+ # Opens the page in a new tab
+ attrs:
+ target: _blank
+---
+```
diff --git a/packages/starlight/__tests__/basics/navigation-labels.test.ts b/packages/starlight/__tests__/basics/navigation-labels.test.ts
index f51cac35..64625350 100644
--- a/packages/starlight/__tests__/basics/navigation-labels.test.ts
+++ b/packages/starlight/__tests__/basics/navigation-labels.test.ts
@@ -20,6 +20,7 @@ describe('getSidebar', () => {
expect(getSidebar('/', undefined)).toMatchInlineSnapshot(`
[
{
+ "attrs": {},
"badge": undefined,
"href": "/",
"isCurrent": true,
@@ -27,6 +28,7 @@ describe('getSidebar', () => {
"type": "link",
},
{
+ "attrs": {},
"badge": undefined,
"href": "/environmental-impact/",
"isCurrent": false,
@@ -37,6 +39,7 @@ describe('getSidebar', () => {
"collapsed": false,
"entries": [
{
+ "attrs": {},
"badge": undefined,
"href": "/guides/authoring-content/",
"isCurrent": false,
@@ -44,6 +47,7 @@ describe('getSidebar', () => {
"type": "link",
},
{
+ "attrs": {},
"badge": undefined,
"href": "/guides/components/",
"isCurrent": false,
diff --git a/packages/starlight/__tests__/basics/navigation-order.test.ts b/packages/starlight/__tests__/basics/navigation-order.test.ts
index 2eb2c88d..089ad8db 100644
--- a/packages/starlight/__tests__/basics/navigation-order.test.ts
+++ b/packages/starlight/__tests__/basics/navigation-order.test.ts
@@ -20,6 +20,7 @@ describe('getSidebar', () => {
"collapsed": false,
"entries": [
{
+ "attrs": {},
"badge": undefined,
"href": "/guides/components/",
"isCurrent": false,
@@ -27,6 +28,7 @@ describe('getSidebar', () => {
"type": "link",
},
{
+ "attrs": {},
"badge": undefined,
"href": "/guides/authoring-content/",
"isCurrent": false,
@@ -38,6 +40,7 @@ describe('getSidebar', () => {
"type": "group",
},
{
+ "attrs": {},
"badge": undefined,
"href": "/environmental-impact/",
"isCurrent": false,
@@ -45,6 +48,7 @@ describe('getSidebar', () => {
"type": "link",
},
{
+ "attrs": {},
"badge": undefined,
"href": "/",
"isCurrent": true,
diff --git a/packages/starlight/__tests__/basics/navigation.test.ts b/packages/starlight/__tests__/basics/navigation.test.ts
index 023edb76..76611322 100644
--- a/packages/starlight/__tests__/basics/navigation.test.ts
+++ b/packages/starlight/__tests__/basics/navigation.test.ts
@@ -18,6 +18,7 @@ describe('getSidebar', () => {
expect(getSidebar('/', undefined)).toMatchInlineSnapshot(`
[
{
+ "attrs": {},
"badge": undefined,
"href": "/",
"isCurrent": true,
@@ -25,6 +26,7 @@ describe('getSidebar', () => {
"type": "link",
},
{
+ "attrs": {},
"badge": undefined,
"href": "/environmental-impact/",
"isCurrent": false,
@@ -35,6 +37,7 @@ describe('getSidebar', () => {
"collapsed": false,
"entries": [
{
+ "attrs": {},
"badge": undefined,
"href": "/guides/authoring-content/",
"isCurrent": false,
@@ -42,6 +45,7 @@ describe('getSidebar', () => {
"type": "link",
},
{
+ "attrs": {},
"badge": undefined,
"href": "/guides/components/",
"isCurrent": false,
@@ -103,6 +107,7 @@ describe('flattenSidebar', () => {
expect(flattened).toMatchInlineSnapshot(`
[
{
+ "attrs": {},
"badge": undefined,
"href": "/",
"isCurrent": true,
@@ -110,6 +115,7 @@ describe('flattenSidebar', () => {
"type": "link",
},
{
+ "attrs": {},
"badge": undefined,
"href": "/environmental-impact/",
"isCurrent": false,
@@ -117,6 +123,7 @@ describe('flattenSidebar', () => {
"type": "link",
},
{
+ "attrs": {},
"badge": undefined,
"href": "/guides/authoring-content/",
"isCurrent": false,
@@ -124,6 +131,7 @@ describe('flattenSidebar', () => {
"type": "link",
},
{
+ "attrs": {},
"badge": undefined,
"href": "/guides/components/",
"isCurrent": false,
@@ -142,6 +150,7 @@ describe('getPrevNextLinks', () => {
expect(links).toMatchInlineSnapshot(`
{
"next": {
+ "attrs": {},
"badge": undefined,
"href": "/guides/authoring-content/",
"isCurrent": false,
@@ -149,6 +158,7 @@ describe('getPrevNextLinks', () => {
"type": "link",
},
"prev": {
+ "attrs": {},
"badge": undefined,
"href": "/",
"isCurrent": false,
@@ -235,6 +245,7 @@ describe('getPrevNextLinks', () => {
href: '/x/',
label: 'X',
isCurrent: false,
+ attrs: {},
});
});
diff --git a/packages/starlight/__tests__/i18n/navigation-order.test.ts b/packages/starlight/__tests__/i18n/navigation-order.test.ts
index 989f8a99..95ecf27c 100644
--- a/packages/starlight/__tests__/i18n/navigation-order.test.ts
+++ b/packages/starlight/__tests__/i18n/navigation-order.test.ts
@@ -30,6 +30,7 @@ describe('getSidebar', () => {
expect(getSidebar('/en/', 'en')).toMatchInlineSnapshot(`
[
{
+ "attrs": {},
"badge": undefined,
"href": "/en/",
"isCurrent": true,
@@ -37,6 +38,7 @@ describe('getSidebar', () => {
"type": "link",
},
{
+ "attrs": {},
"badge": undefined,
"href": "/en/404/",
"isCurrent": false,
@@ -47,6 +49,7 @@ describe('getSidebar', () => {
"collapsed": false,
"entries": [
{
+ "attrs": {},
"badge": undefined,
"href": "/en/guides/authoring-content/",
"isCurrent": false,
@@ -65,6 +68,7 @@ describe('getSidebar', () => {
expect(getSidebar('/fr/', 'fr')).toMatchInlineSnapshot(`
[
{
+ "attrs": {},
"badge": undefined,
"href": "/fr/",
"isCurrent": true,
@@ -72,6 +76,7 @@ describe('getSidebar', () => {
"type": "link",
},
{
+ "attrs": {},
"badge": undefined,
"href": "/fr/404/",
"isCurrent": false,
@@ -82,6 +87,7 @@ describe('getSidebar', () => {
"collapsed": false,
"entries": [
{
+ "attrs": {},
"badge": undefined,
"href": "/fr/guides/authoring-content/",
"isCurrent": false,
@@ -96,6 +102,7 @@ describe('getSidebar', () => {
"collapsed": false,
"entries": [
{
+ "attrs": {},
"badge": undefined,
"href": "/fr/référence/bénéfice/",
"isCurrent": false,
@@ -103,6 +110,7 @@ describe('getSidebar', () => {
"type": "link",
},
{
+ "attrs": {},
"badge": undefined,
"href": "/fr/référence/bricolage/",
"isCurrent": false,
@@ -117,6 +125,7 @@ describe('getSidebar', () => {
"collapsed": false,
"entries": [
{
+ "attrs": {},
"badge": undefined,
"href": "/fr/route/décoder/",
"isCurrent": false,
@@ -124,6 +133,7 @@ describe('getSidebar', () => {
"type": "link",
},
{
+ "attrs": {},
"badge": undefined,
"href": "/fr/route/distribuer/",
"isCurrent": false,
diff --git a/packages/starlight/__tests__/sidebar/navigation-attributes.test.ts b/packages/starlight/__tests__/sidebar/navigation-attributes.test.ts
new file mode 100644
index 00000000..a6c64961
--- /dev/null
+++ b/packages/starlight/__tests__/sidebar/navigation-attributes.test.ts
@@ -0,0 +1,93 @@
+import { describe, expect, test, vi } from 'vitest';
+import { getSidebar } from '../../utils/navigation';
+
+vi.mock('astro:content', async () =>
+ (await import('../test-utils')).mockedAstroContent({
+ docs: [
+ ['index.mdx', { title: 'Home Page' }],
+ ['environmental-impact.md', { title: 'Eco-friendly docs' }],
+ [
+ 'reference/frontmatter.md',
+ {
+ title: 'Frontmatter Reference',
+ sidebar: { attrs: { class: 'advanced', ping: 'https://example.com' } },
+ },
+ ],
+ ],
+ })
+);
+
+describe('getSidebar', () => {
+ test('passes down custom HTML link attributes', () => {
+ expect(getSidebar('/', undefined)).toMatchInlineSnapshot(`
+ [
+ {
+ "attrs": {},
+ "badge": undefined,
+ "href": "/",
+ "isCurrent": true,
+ "label": "Home",
+ "type": "link",
+ },
+ {
+ "collapsed": false,
+ "entries": [
+ {
+ "attrs": {},
+ "badge": {
+ "text": "New",
+ "variant": "success",
+ },
+ "href": "/intro/",
+ "isCurrent": false,
+ "label": "Introduction",
+ "type": "link",
+ },
+ {
+ "attrs": {},
+ "badge": {
+ "text": "Deprecated",
+ "variant": "default",
+ },
+ "href": "/next-steps/",
+ "isCurrent": false,
+ "label": "Next Steps",
+ "type": "link",
+ },
+ {
+ "attrs": {
+ "class": "showcase-link",
+ "target": "_blank",
+ },
+ "badge": undefined,
+ "href": "/showcase/",
+ "isCurrent": false,
+ "label": "Showcase",
+ "type": "link",
+ },
+ ],
+ "label": "Start Here",
+ "type": "group",
+ },
+ {
+ "collapsed": false,
+ "entries": [
+ {
+ "attrs": {
+ "class": "advanced",
+ "ping": "https://example.com",
+ },
+ "badge": undefined,
+ "href": "/reference/frontmatter/",
+ "isCurrent": false,
+ "label": "Frontmatter Reference",
+ "type": "link",
+ },
+ ],
+ "label": "Reference",
+ "type": "group",
+ },
+ ]
+ `);
+ });
+});
diff --git a/packages/starlight/__tests__/sidebar/navigation-badges.test.ts b/packages/starlight/__tests__/sidebar/navigation-badges.test.ts
index 6c4b1863..cfde6293 100644
--- a/packages/starlight/__tests__/sidebar/navigation-badges.test.ts
+++ b/packages/starlight/__tests__/sidebar/navigation-badges.test.ts
@@ -29,6 +29,7 @@ describe('getSidebar', () => {
expect(getSidebar('/', undefined)).toMatchInlineSnapshot(`
[
{
+ "attrs": {},
"badge": undefined,
"href": "/",
"isCurrent": true,
@@ -39,6 +40,7 @@ describe('getSidebar', () => {
"collapsed": false,
"entries": [
{
+ "attrs": {},
"badge": {
"text": "New",
"variant": "success",
@@ -49,6 +51,7 @@ describe('getSidebar', () => {
"type": "link",
},
{
+ "attrs": {},
"badge": {
"text": "Deprecated",
"variant": "default",
@@ -58,6 +61,17 @@ describe('getSidebar', () => {
"label": "Next Steps",
"type": "link",
},
+ {
+ "attrs": {
+ "class": "showcase-link",
+ "target": "_blank",
+ },
+ "badge": undefined,
+ "href": "/showcase/",
+ "isCurrent": false,
+ "label": "Showcase",
+ "type": "link",
+ },
],
"label": "Start Here",
"type": "group",
@@ -66,6 +80,7 @@ describe('getSidebar', () => {
"collapsed": false,
"entries": [
{
+ "attrs": {},
"badge": {
"text": "Experimental",
"variant": "tip",
@@ -76,6 +91,7 @@ describe('getSidebar', () => {
"type": "link",
},
{
+ "attrs": {},
"badge": {
"text": "New",
"variant": "default",
diff --git a/packages/starlight/__tests__/sidebar/navigation-hidden.test.ts b/packages/starlight/__tests__/sidebar/navigation-hidden.test.ts
index df539ef7..db45111d 100644
--- a/packages/starlight/__tests__/sidebar/navigation-hidden.test.ts
+++ b/packages/starlight/__tests__/sidebar/navigation-hidden.test.ts
@@ -18,6 +18,7 @@ describe('getSidebar', () => {
expect(getSidebar('/', undefined)).toMatchInlineSnapshot(`
[
{
+ "attrs": {},
"badge": undefined,
"href": "/",
"isCurrent": true,
@@ -28,6 +29,7 @@ describe('getSidebar', () => {
"collapsed": false,
"entries": [
{
+ "attrs": {},
"badge": {
"text": "New",
"variant": "success",
@@ -38,6 +40,7 @@ describe('getSidebar', () => {
"type": "link",
},
{
+ "attrs": {},
"badge": {
"text": "Deprecated",
"variant": "default",
@@ -47,6 +50,17 @@ describe('getSidebar', () => {
"label": "Next Steps",
"type": "link",
},
+ {
+ "attrs": {
+ "class": "showcase-link",
+ "target": "_blank",
+ },
+ "badge": undefined,
+ "href": "/showcase/",
+ "isCurrent": false,
+ "label": "Showcase",
+ "type": "link",
+ },
],
"label": "Start Here",
"type": "group",
@@ -55,6 +69,7 @@ describe('getSidebar', () => {
"collapsed": false,
"entries": [
{
+ "attrs": {},
"badge": undefined,
"href": "/reference/configuration/",
"isCurrent": false,
diff --git a/packages/starlight/__tests__/sidebar/navigation-order.test.ts b/packages/starlight/__tests__/sidebar/navigation-order.test.ts
index f0257bc5..e5b5d367 100644
--- a/packages/starlight/__tests__/sidebar/navigation-order.test.ts
+++ b/packages/starlight/__tests__/sidebar/navigation-order.test.ts
@@ -18,6 +18,7 @@ describe('getSidebar', () => {
expect(getSidebar('/', undefined)).toMatchInlineSnapshot(`
[
{
+ "attrs": {},
"badge": undefined,
"href": "/",
"isCurrent": true,
@@ -28,6 +29,7 @@ describe('getSidebar', () => {
"collapsed": false,
"entries": [
{
+ "attrs": {},
"badge": {
"text": "New",
"variant": "success",
@@ -38,6 +40,7 @@ describe('getSidebar', () => {
"type": "link",
},
{
+ "attrs": {},
"badge": {
"text": "Deprecated",
"variant": "default",
@@ -47,6 +50,17 @@ describe('getSidebar', () => {
"label": "Next Steps",
"type": "link",
},
+ {
+ "attrs": {
+ "class": "showcase-link",
+ "target": "_blank",
+ },
+ "badge": undefined,
+ "href": "/showcase/",
+ "isCurrent": false,
+ "label": "Showcase",
+ "type": "link",
+ },
],
"label": "Start Here",
"type": "group",
@@ -55,6 +69,7 @@ describe('getSidebar', () => {
"collapsed": false,
"entries": [
{
+ "attrs": {},
"badge": undefined,
"href": "/reference/frontmatter/",
"isCurrent": false,
@@ -62,6 +77,7 @@ describe('getSidebar', () => {
"type": "link",
},
{
+ "attrs": {},
"badge": undefined,
"href": "/reference/configuration/",
"isCurrent": false,
diff --git a/packages/starlight/__tests__/sidebar/navigation.test.ts b/packages/starlight/__tests__/sidebar/navigation.test.ts
index dd580740..9ea3cdb4 100644
--- a/packages/starlight/__tests__/sidebar/navigation.test.ts
+++ b/packages/starlight/__tests__/sidebar/navigation.test.ts
@@ -18,6 +18,7 @@ describe('getSidebar', () => {
expect(getSidebar('/', undefined)).toMatchInlineSnapshot(`
[
{
+ "attrs": {},
"badge": undefined,
"href": "/",
"isCurrent": true,
@@ -28,6 +29,7 @@ describe('getSidebar', () => {
"collapsed": false,
"entries": [
{
+ "attrs": {},
"badge": {
"text": "New",
"variant": "success",
@@ -38,6 +40,7 @@ describe('getSidebar', () => {
"type": "link",
},
{
+ "attrs": {},
"badge": {
"text": "Deprecated",
"variant": "default",
@@ -47,6 +50,17 @@ describe('getSidebar', () => {
"label": "Next Steps",
"type": "link",
},
+ {
+ "attrs": {
+ "class": "showcase-link",
+ "target": "_blank",
+ },
+ "badge": undefined,
+ "href": "/showcase/",
+ "isCurrent": false,
+ "label": "Showcase",
+ "type": "link",
+ },
],
"label": "Start Here",
"type": "group",
@@ -55,6 +69,7 @@ describe('getSidebar', () => {
"collapsed": false,
"entries": [
{
+ "attrs": {},
"badge": undefined,
"href": "/reference/configuration/",
"isCurrent": false,
@@ -62,6 +77,7 @@ describe('getSidebar', () => {
"type": "link",
},
{
+ "attrs": {},
"badge": undefined,
"href": "/reference/frontmatter/",
"isCurrent": false,
diff --git a/packages/starlight/__tests__/sidebar/vitest.config.ts b/packages/starlight/__tests__/sidebar/vitest.config.ts
index 31860cae..31457b7a 100644
--- a/packages/starlight/__tests__/sidebar/vitest.config.ts
+++ b/packages/starlight/__tests__/sidebar/vitest.config.ts
@@ -18,6 +18,11 @@ export default defineVitestConfig({
},
},
{ label: 'Next Steps', link: '/next-steps', badge: 'Deprecated' },
+ {
+ label: 'Showcase',
+ link: '/showcase',
+ attrs: { class: 'showcase-link', target: '_blank' },
+ },
],
},
// A group linking to all pages in the reference directory.
diff --git a/packages/starlight/components/SidebarSublist.astro b/packages/starlight/components/SidebarSublist.astro
index 08c3f3b8..393edf58 100644
--- a/packages/starlight/components/SidebarSublist.astro
+++ b/packages/starlight/components/SidebarSublist.astro
@@ -17,7 +17,8 @@ interface Props {
<a
href={entry.href}
aria-current={entry.isCurrent && 'page'}
- class:list={{ large: !Astro.props.nested }}
+ class:list={[{ large: !Astro.props.nested }, entry.attrs.class]}
+ {...entry.attrs}
>
<span>{entry.label}</span>
{entry.badge && (
diff --git a/packages/starlight/schema.ts b/packages/starlight/schema.ts
index 89fc47e2..5f73dfdc 100644
--- a/packages/starlight/schema.ts
+++ b/packages/starlight/schema.ts
@@ -5,6 +5,7 @@ import { PrevNextLinkConfigSchema } from './schemas/prevNextLink';
import { TableOfContentsSchema } from './schemas/tableOfContents';
import { Icons } from './components/Icons';
import { BadgeConfigSchema } from './schemas/badge';
+import { SidebarLinkItemHTMLAttributesSchema } from './schemas/sidebar';
export { i18nSchema } from './schemas/i18n';
type IconName = keyof typeof Icons;
@@ -139,6 +140,8 @@ export function docsSchema() {
* Passing only a string defaults to the 'default' variant which uses the site accent color.
*/
badge: BadgeConfigSchema(),
+ /** HTML attributes to add to the sidebar link. */
+ attrs: SidebarLinkItemHTMLAttributesSchema(),
})
.default({}),
diff --git a/packages/starlight/schemas/sidebar.ts b/packages/starlight/schemas/sidebar.ts
new file mode 100644
index 00000000..176a77cd
--- /dev/null
+++ b/packages/starlight/schemas/sidebar.ts
@@ -0,0 +1,89 @@
+import type { AstroBuiltinAttributes } from 'astro';
+import type { HTMLAttributes } from 'astro/types';
+import { z } from 'astro/zod';
+import { BadgeConfigSchema } from './badge';
+
+const SidebarBaseSchema = z.object({
+ /** The visible label for this item in the sidebar. */
+ label: z.string(),
+ /** Translations of the `label` for each supported language. */
+ translations: z.record(z.string()).default({}),
+});
+
+const SidebarGroupSchema = SidebarBaseSchema.extend({
+ /** Whether this item should be collapsed by default. */
+ collapsed: z.boolean().default(false),
+});
+
+// HTML attributes that can be added to an anchor element, validated as
+// `Record<string, string | number | boolean | undefined>` but typed as `HTMLAttributes<'a'>`
+// for user convenience.
+const linkHTMLAttributesSchema = z.record(
+ z.union([z.string(), z.number(), z.boolean(), z.undefined()])
+) as z.Schema<Omit<HTMLAttributes<'a'>, keyof AstroBuiltinAttributes | 'children'>>;
+export type LinkHTMLAttributes = z.infer<typeof linkHTMLAttributesSchema>;
+
+export const SidebarLinkItemHTMLAttributesSchema = () => linkHTMLAttributesSchema.default({});
+
+const SidebarLinkItemSchema = SidebarBaseSchema.extend({
+ /** The link to this item’s content. Can be a relative link to local files or the full URL of an external page. */
+ link: z.string(),
+ /** Adds a badge to the link item */
+ badge: BadgeConfigSchema(),
+ /** HTML attributes to add to the link item. */
+ attrs: SidebarLinkItemHTMLAttributesSchema(),
+});
+export type SidebarLinkItem = z.infer<typeof SidebarLinkItemSchema>;
+
+const AutoSidebarGroupSchema = SidebarGroupSchema.extend({
+ /** Enable autogenerating a sidebar category from a specific docs directory. */
+ autogenerate: z.object({
+ /** The directory to generate sidebar items for. */
+ directory: z.string(),
+ /**
+ * Whether the autogenerated subgroups should be collapsed by default.
+ * Defaults to the `AutoSidebarGroup` `collapsed` value.
+ */
+ collapsed: z.boolean().optional(),
+ // TODO: not supported by Docusaurus but would be good to have
+ /** How many directories deep to include from this directory in the sidebar. Default: `Infinity`. */
+ // depth: z.number().optional(),
+ }),
+});
+export type AutoSidebarGroup = z.infer<typeof AutoSidebarGroupSchema>;
+
+type ManualSidebarGroupInput = z.input<typeof SidebarGroupSchema> & {
+ /** Array of links and subcategories to display in this category. */
+ items: Array<
+ | z.input<typeof SidebarLinkItemSchema>
+ | z.input<typeof AutoSidebarGroupSchema>
+ | ManualSidebarGroupInput
+ >;
+};
+
+type ManualSidebarGroupOutput = z.output<typeof SidebarGroupSchema> & {
+ /** Array of links and subcategories to display in this category. */
+ items: Array<
+ | z.output<typeof SidebarLinkItemSchema>
+ | z.output<typeof AutoSidebarGroupSchema>
+ | ManualSidebarGroupOutput
+ >;
+};
+
+const ManualSidebarGroupSchema: z.ZodType<
+ ManualSidebarGroupOutput,
+ z.ZodTypeDef,
+ ManualSidebarGroupInput
+> = SidebarGroupSchema.extend({
+ /** Array of links and subcategories to display in this category. */
+ items: z.lazy(() =>
+ z.union([SidebarLinkItemSchema, ManualSidebarGroupSchema, AutoSidebarGroupSchema]).array()
+ ),
+});
+
+export const SidebarItemSchema = z.union([
+ SidebarLinkItemSchema,
+ ManualSidebarGroupSchema,
+ AutoSidebarGroupSchema,
+]);
+export type SidebarItem = z.infer<typeof SidebarItemSchema>;
diff --git a/packages/starlight/utils/navigation.ts b/packages/starlight/utils/navigation.ts
index 88d9e3e1..a8af46b5 100644
--- a/packages/starlight/utils/navigation.ts
+++ b/packages/starlight/utils/navigation.ts
@@ -5,9 +5,14 @@ import { pathWithBase } from './base';
import { pickLang } from './i18n';
import { getLocaleRoutes, type Route } from './routing';
import { localeToLang, slugToPathname } from './slugs';
-import type { AutoSidebarGroup, SidebarItem, SidebarLinkItem } from './user-config';
import { ensureLeadingAndTrailingSlashes, ensureTrailingSlash } from './path';
import type { Badge } from '../schemas/badge';
+import type {
+ AutoSidebarGroup,
+ LinkHTMLAttributes,
+ SidebarItem,
+ SidebarLinkItem,
+} from '../schemas/sidebar';
const DirKey = Symbol('DirKey');
@@ -17,6 +22,7 @@ export interface Link {
href: string;
isCurrent: boolean;
badge: Badge | undefined;
+ attrs: LinkHTMLAttributes;
}
interface Group {
@@ -114,14 +120,20 @@ function linkFromConfig(
if (locale) href = '/' + locale + href;
}
const label = pickLang(item.translations, localeToLang(locale)) || item.label;
- return makeLink(href, label, currentPathname, item.badge);
+ return makeLink(href, label, currentPathname, item.badge, item.attrs);
}
/** Create a link entry. */
-function makeLink(href: string, label: string, currentPathname: string, badge?: Badge): Link {
+function makeLink(
+ href: string,
+ label: string,
+ currentPathname: string,
+ badge?: Badge,
+ attrs?: LinkHTMLAttributes
+): Link {
if (!isAbsolute(href)) href = pathWithBase(href);
const isCurrent = href === ensureTrailingSlash(currentPathname);
- return { type: 'link', label, href, isCurrent, badge };
+ return { type: 'link', label, href, isCurrent, badge, attrs: attrs ?? {} };
}
/** Get the segments leading to a page. */
@@ -171,7 +183,8 @@ function linkFromRoute(route: Route, currentPathname: string): Link {
slugToPathname(route.slug),
route.entry.data.sidebar.label || route.entry.data.title,
currentPathname,
- route.entry.data.sidebar.badge
+ route.entry.data.sidebar.badge,
+ route.entry.data.sidebar.attrs
);
}
@@ -308,6 +321,8 @@ function applyPrevNextLinkConfig(
...link,
label: config.label ?? link.label,
href: config.link ?? link.href,
+ // Explicitly remove sidebar link attributes for prev/next links.
+ attrs: {},
};
} else if (config.link && config.label) {
// If there is no link and the frontmatter contains both a URL and a label,
diff --git a/packages/starlight/utils/user-config.ts b/packages/starlight/utils/user-config.ts
index ccaf0745..4cb01293 100644
--- a/packages/starlight/utils/user-config.ts
+++ b/packages/starlight/utils/user-config.ts
@@ -4,7 +4,7 @@ import { HeadConfigSchema } from '../schemas/head';
import { LogoConfigSchema } from '../schemas/logo';
import { TableOfContentsSchema } from '../schemas/tableOfContents';
import { FaviconSchema } from '../schemas/favicon';
-import { BadgeConfigSchema } from '../schemas/badge';
+import { SidebarItemSchema } from '../schemas/sidebar';
const LocaleSchema = z.object({
/** The label for this language to show in UI, e.g. `"English"`, `"العربية"`, or `"简体中文"`. */
@@ -28,79 +28,6 @@ const LocaleSchema = z.object({
),
});
-const SidebarBaseSchema = z.object({
- /** The visible label for this item in the sidebar. */
- label: z.string(),
- /** Translations of the `label` for each supported language. */
- translations: z.record(z.string()).default({}),
-});
-
-const SidebarGroupSchema = SidebarBaseSchema.extend({
- /** Whether this item should be collapsed by default. */
- collapsed: z.boolean().default(false),
-});
-
-const SidebarLinkItemSchema = SidebarBaseSchema.extend({
- /** The link to this item’s content. Can be a relative link to local files or the full URL of an external page. */
- link: z.string(),
- /** Adds a badge to the link item */
- badge: BadgeConfigSchema(),
-});
-export type SidebarLinkItem = z.infer<typeof SidebarLinkItemSchema>;
-
-const AutoSidebarGroupSchema = SidebarGroupSchema.extend({
- /** Enable autogenerating a sidebar category from a specific docs directory. */
- autogenerate: z.object({
- /** The directory to generate sidebar items for. */
- directory: z.string(),
- /**
- * Whether the autogenerated subgroups should be collapsed by default.
- * Defaults to the `AutoSidebarGroup` `collapsed` value.
- */
- collapsed: z.boolean().optional(),
- // TODO: not supported by Docusaurus but would be good to have
- /** How many directories deep to include from this directory in the sidebar. Default: `Infinity`. */
- // depth: z.number().optional(),
- }),
-});
-export type AutoSidebarGroup = z.infer<typeof AutoSidebarGroupSchema>;
-
-type ManualSidebarGroupInput = z.input<typeof SidebarGroupSchema> & {
- /** Array of links and subcategories to display in this category. */
- items: Array<
- | z.input<typeof SidebarLinkItemSchema>
- | z.input<typeof AutoSidebarGroupSchema>
- | ManualSidebarGroupInput
- >;
-};
-
-type ManualSidebarGroupOutput = z.output<typeof SidebarGroupSchema> & {
- /** Array of links and subcategories to display in this category. */
- items: Array<
- | z.output<typeof SidebarLinkItemSchema>
- | z.output<typeof AutoSidebarGroupSchema>
- | ManualSidebarGroupOutput
- >;
-};
-
-const ManualSidebarGroupSchema: z.ZodType<
- ManualSidebarGroupOutput,
- z.ZodTypeDef,
- ManualSidebarGroupInput
-> = SidebarGroupSchema.extend({
- /** Array of links and subcategories to display in this category. */
- items: z.lazy(() =>
- z.union([SidebarLinkItemSchema, ManualSidebarGroupSchema, AutoSidebarGroupSchema]).array()
- ),
-});
-
-const SidebarItemSchema = z.union([
- SidebarLinkItemSchema,
- ManualSidebarGroupSchema,
- AutoSidebarGroupSchema,
-]);
-export type SidebarItem = z.infer<typeof SidebarItemSchema>;
-
const UserConfigSchema = z.object({
/** Title for your website. Will be used in metadata and as browser tab title. */
title: z
diff --git a/packages/starlight/vitest.config.ts b/packages/starlight/vitest.config.ts
index b80690dd..ab9b2f4f 100644
--- a/packages/starlight/vitest.config.ts
+++ b/packages/starlight/vitest.config.ts
@@ -22,10 +22,10 @@ export default defineConfig({
reportsDirectory: './__coverage__',
exclude: [...defaultCoverageExcludes, '**/vitest.*', 'components.ts', 'types.ts'],
thresholdAutoUpdate: true,
- lines: 66.57,
- functions: 88.46,
- branches: 90.14,
- statements: 66.57,
+ lines: 69.21,
+ functions: 90.24,
+ branches: 90.62,
+ statements: 69.21,
},
},
});