Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use Starlight sidebar user-config format for <StarlightPage /> sidebar prop #2168

Merged
merged 5 commits into from
Aug 16, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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) like the Starlight global `sidebar` configuration.
delucis marked this conversation as resolved.
Show resolved Hide resolved

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:

```tsx
delucis marked this conversation as resolved.
Show resolved Hide resolved
---
// 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:

```tsx
delucis marked this conversation as resolved.
Show resolved Hide resolved
---
// 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
Loading