Skip to content

Commit

Permalink
Use Starlight sidebar user-config format for <StarlightPage /> `s…
Browse files Browse the repository at this point in the history
…idebar` prop (#2168)

Co-authored-by: Chris Swithinbank <swithinbank@gmail.com>
  • Loading branch information
HiDeoo and delucis authored Aug 16, 2024
1 parent 68f56a7 commit e044fee
Show file tree
Hide file tree
Showing 5 changed files with 213 additions and 211 deletions.
56 changes: 56 additions & 0 deletions .changeset/chilled-kiwis-count.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
---
'@astrojs/starlight': minor
---

⚠️ **BREAKING CHANGE:** Updates the `<StarlightPage />` component `sidebar` prop to accept an array of [`SidebarItem`](https://starlight.astro.build/reference/configuration/#sidebaritem)s like the main Starlight `sidebar` configuration in `astro.config.mjs`.

This change simplifies the definition of sidebar items in the `<StarlightPage />` component, allows for shared sidebar configuration between the global `sidebar` option and `<StarlightPage />` component, and also enables the usage of autogenerated sidebar groups with the `<StarlightPage />` component.
If you are using the `<StarlightPage />` component with a custom `sidebar` configuration, you will need to update the `sidebar` prop to an array of [`SidebarItem`](https://starlight.astro.build/reference/configuration/#sidebaritem) objects.

For example, the following custom page with a custom `sidebar` configuration defines a “Resources” group with a “New” badge, a link to the “Showcase” page which is part of the `docs` content collection, and a link to the Starlight website:

```astro
---
// src/pages/custom-page/example.astro
---
<StarlightPage
frontmatter={{ title: 'My custom page' }}
sidebar={[
{
type: 'group',
label: 'Resources',
badge: { text: 'New' },
items: [
{ type: 'link', label: 'Showcase', href: '/showcase/' },
{ type: 'link', label: 'Starlight', href: 'https://starlight.astro.build/' },
],
},
]}
>
<p>This is a custom page with a custom component.</p>
</StarlightPage>
```

This configuration will now need to be updated to the following:

```astro
---
// src/pages/custom-page/example.astro
---
<StarlightPage
frontmatter={{ title: 'My custom page' }}
sidebar={[
{
label: 'Resources',
badge: { text: 'New' },
items: ['showcase', { label: 'Starlight', link: 'https://starlight.astro.build/' }],
},
]}
>
<p>This is a custom page with a custom component.</p>
</StarlightPage>
```

See the [“Sidebar Navigation”](https://starlight.astro.build/guides/sidebar/) guide to learn more about the available options for customizing the sidebar.
15 changes: 8 additions & 7 deletions docs/src/content/docs/guides/pages.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -109,26 +109,25 @@ The following properties differ from Markdown frontmatter:

##### `sidebar`

**type:** `SidebarEntry[]`
**type:** [`SidebarItem[]`](/reference/configuration/#sidebaritem)
**default:** the sidebar generated based on the [global `sidebar` config](/reference/configuration/#sidebar)

Provide a custom site navigation sidebar for this page.
If not set, the page will use the default global sidebar.

For example, the following page overrides the default sidebar with a link to the homepage and a group of links to different constellations.
The current page in the sidebar is set using the `isCurrent` property and an optional `badge` has been added to a link item.
For example, the following page overrides the default sidebar with a link to the homepage and a group of links to various other custom pages.

```astro {3-13}
<StarlightPage
frontmatter={{ title: 'Orion' }}
sidebar={[
{ label: 'Home', href: '/' },
{ label: 'Home', link: '/' },
{
label: 'Constellations',
items: [
{ label: 'Andromeda', href: '/andromeda/' },
{ label: 'Orion', href: '/orion/', isCurrent: true },
{ label: 'Ursa Minor', href: '/ursa-minor/', badge: 'Stub' },
{ label: 'Andromeda', link: '/andromeda/' },
{ label: 'Orion', link: '/orion/' },
{ label: 'Ursa Minor', link: '/ursa-minor/', badge: 'Stub' },
],
},
]}
Expand All @@ -137,6 +136,8 @@ The current page in the sidebar is set using the `isCurrent` property and an opt
</StarlightPage>
```

See the [“Sidebar Navigation”](/guides/sidebar/) guide to learn more about the available options for customizing the sidebar.

##### `hasSidebar`

**type:** `boolean`
Expand Down
235 changes: 122 additions & 113 deletions packages/starlight/__tests__/basics/starlight-page-route-data.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { assert, expect, test, vi } from 'vitest';
import { expect, test, vi } from 'vitest';
import { generateRouteData } from '../../utils/route-data';
import { routes } from '../../utils/routing';
import {
Expand All @@ -15,6 +15,9 @@ vi.mock('astro:content', async () =>
docs: [
['index.mdx', { title: 'Home Page' }],
['getting-started.mdx', { title: 'Getting Started' }],
['guides/authoring-content.md', { title: 'Authoring Markdown' }],
['guides/components.mdx', { title: 'Components' }],
['reference/frontmatter.md', { title: 'Frontmatter Reference' }],
],
})
);
Expand Down Expand Up @@ -102,10 +105,64 @@ test('uses generated sidebar when no sidebar is provided', async () => {
props: starlightPageProps,
url: starlightPageUrl,
});
expect(data.sidebar.map((entry) => entry.label)).toMatchInlineSnapshot(`
expect(data.sidebar).toMatchInlineSnapshot(`
[
"Home Page",
"Getting Started",
{
"attrs": {},
"badge": undefined,
"href": "/",
"isCurrent": false,
"label": "Home Page",
"type": "link",
},
{
"attrs": {},
"badge": undefined,
"href": "/getting-started/",
"isCurrent": false,
"label": "Getting Started",
"type": "link",
},
{
"badge": undefined,
"collapsed": false,
"entries": [
{
"attrs": {},
"badge": undefined,
"href": "/guides/authoring-content/",
"isCurrent": false,
"label": "Authoring Markdown",
"type": "link",
},
{
"attrs": {},
"badge": undefined,
"href": "/guides/components/",
"isCurrent": false,
"label": "Components",
"type": "link",
},
],
"label": "guides",
"type": "group",
},
{
"badge": undefined,
"collapsed": false,
"entries": [
{
"attrs": {},
"badge": undefined,
"href": "/reference/frontmatter/",
"isCurrent": false,
"label": "Frontmatter Reference",
"type": "link",
},
],
"label": "reference",
"type": "group",
},
]
`);
});
Expand All @@ -116,98 +173,76 @@ test('uses provided sidebar if any', async () => {
...starlightPageProps,
sidebar: [
{
type: 'link',
label: 'Custom link 1',
href: '/test/1',
isCurrent: false,
badge: undefined,
attrs: {},
link: '/test/1',
badge: 'New',
},
{
type: 'link',
label: 'Custom link 2',
href: '/test/2',
isCurrent: false,
badge: undefined,
attrs: {},
link: '/test/2',
},
],
},
url: starlightPageUrl,
});
expect(data.sidebar.map((entry) => entry.label)).toMatchInlineSnapshot(`
[
"Custom link 1",
"Custom link 2",
]
`);
});

test('uses provided sidebar with minimal config', async () => {
const data = await generateStarlightPageRouteData({
props: {
...starlightPageProps,
sidebar: [
{ label: 'Custom link 1', href: '/test/1' },
{ label: 'Custom link 2', href: '/test/2' },
],
},
url: starlightPageUrl,
});
expect(data.sidebar.map((entry) => entry.label)).toMatchInlineSnapshot(`
[
"Custom link 1",
"Custom link 2",
]
`);
});

test('supports deprecated `entries` field for sidebar groups', async () => {
const data = await generateStarlightPageRouteData({
props: {
...starlightPageProps,
sidebar: [
{
label: 'Group',
entries: [
{ label: 'Custom link 1', href: '/test/1' },
{ label: 'Custom link 2', href: '/test/2' },
],
label: 'Guides',
autogenerate: { directory: 'guides' },
},
'reference/frontmatter',
],
},
url: starlightPageUrl,
});
assert(data.sidebar[0]!.type === 'group');
expect(data.sidebar[0]!.entries.map((entry) => entry.label)).toMatchInlineSnapshot(`
expect(data.sidebar).toMatchInlineSnapshot(`
[
"Custom link 1",
"Custom link 2",
]
`);
});

test('supports `items` field for sidebar groups', async () => {
const data = await generateStarlightPageRouteData({
props: {
...starlightPageProps,
sidebar: [
{
label: 'Group',
items: [
{ label: 'Custom link 1', href: '/test/1' },
{ label: 'Custom link 2', href: '/test/2' },
],
},
],
},
url: starlightPageUrl,
});
assert(data.sidebar[0]!.type === 'group');
expect(data.sidebar[0]!.entries.map((entry) => entry.label)).toMatchInlineSnapshot(`
[
"Custom link 1",
"Custom link 2",
{
"attrs": {},
"badge": {
"text": "New",
"variant": "default",
},
"href": "/test/1",
"isCurrent": false,
"label": "Custom link 1",
"type": "link",
},
{
"attrs": {},
"badge": undefined,
"href": "/test/2",
"isCurrent": false,
"label": "Custom link 2",
"type": "link",
},
{
"badge": undefined,
"collapsed": false,
"entries": [
{
"attrs": {},
"badge": undefined,
"href": "/guides/authoring-content/",
"isCurrent": false,
"label": "Authoring Markdown",
"type": "link",
},
{
"attrs": {},
"badge": undefined,
"href": "/guides/components/",
"isCurrent": false,
"label": "Components",
"type": "link",
},
],
"label": "Guides",
"type": "group",
},
{
"attrs": {},
"badge": undefined,
"href": "/reference/frontmatter",
"isCurrent": false,
"label": "Frontmatter Reference",
"type": "link",
},
]
`);
});
Expand All @@ -221,34 +256,7 @@ test('throws error if sidebar is malformated', async () => {
{
label: 'Custom link 1',
//@ts-expect-error Intentionally bad type to cause error.
href: 5,
},
],
},
url: starlightPageUrl,
})
).rejects.toThrowErrorMatchingInlineSnapshot(`
"[AstroUserError]:
Invalid sidebar prop passed to the \`<StarlightPage/>\` component.
Hint:
**0**: Did not match union.
> Expected type \`{ href: string } | { entries: array }\`
> Received \`{ "label": "Custom link 1", "href": 5 }\`"
`);
});

test('throws error if sidebar uses wrong literal for entry type', async () => {
// This test also makes sure we show a helpful error for incorrect literals.
expect(() =>
generateStarlightPageRouteData({
props: {
...starlightPageProps,
sidebar: [
{
//@ts-expect-error Intentionally bad type to cause error.
type: 'typo',
label: 'Custom link 1',
href: '/',
href: '/test/1',
},
],
},
Expand All @@ -259,7 +267,8 @@ test('throws error if sidebar uses wrong literal for entry type', async () => {
Invalid sidebar prop passed to the \`<StarlightPage/>\` component.
Hint:
**0**: Did not match union.
> **0.type**: Expected \`"link" | "group"\`, received \`"typo"\`"
> Expected type \`{ link: string; } | { items: array; } | { autogenerate: object; } | { slug: string } | string\`
> Received \`{ "label": "Custom link 1", "href": "/test/1" }\`"
`);
});

Expand Down
Loading

0 comments on commit e044fee

Please sign in to comment.