diff --git a/playground/astro.config.mjs b/playground/astro.config.mjs
index 2d4fa3b..2aaa7e9 100644
--- a/playground/astro.config.mjs
+++ b/playground/astro.config.mjs
@@ -111,6 +111,28 @@ export default defineConfig({
style: 'font-size:1.25rem; margin: 0 -0.125rem;',
},
}],
+ subNavBar: [
+ {
+ label: 'Blog',
+ slug: 'posts',
+ },
+ {
+ label: 'Talks',
+ slug: 'talks',
+ },
+ {
+ label: 'Podcasts',
+ slug: 'podcasts',
+ },
+ {
+ label: 'Streams',
+ slug: 'streams',
+ },
+ {
+ label: 'Notes',
+ slug: 'notes',
+ },
+ ],
}),
],
})
diff --git a/src/components/SubNav.astro b/src/components/SubNav.astro
index 55c7947..5d5913e 100644
--- a/src/components/SubNav.astro
+++ b/src/components/SubNav.astro
@@ -1,20 +1,26 @@
---
-const inactiveStyle = 'opacity-20 hover:opacity-50'
-const activeStyle = 'opacity-100 underline'
+import { getSubNavbarFromConfig } from '../utils/navigation'
+import { slugToLocaleData, urlToSlug } from '../utils/slugs'
+import config from 'virtual:vitesse/user-config'
-const currentPath = Astro.url.pathname
+const url = Astro.url
+const slug = urlToSlug(url)
+const localeData = slugToLocaleData(slug)
+const subNavBar = getSubNavbarFromConfig(config.subNavBar, url.pathname, localeData.locale)
---
diff --git a/src/schemas/navbar.ts b/src/schemas/navbar.ts
index 9d3f256..dd236d6 100644
--- a/src/schemas/navbar.ts
+++ b/src/schemas/navbar.ts
@@ -7,6 +7,9 @@ const NavBarBaseSchema = z.object({
label: z.string(),
/** Translations of the `label` for each supported language. */
translations: z.record(z.string()).default({}),
+})
+
+const NavBarWithIconSchema = NavBarBaseSchema.extend({
icon: z.string().optional(),
hideLabel: z.boolean().default(false),
labelClass: z.string().optional().default(''),
@@ -24,7 +27,7 @@ export type LinkHTMLAttributes = z.infer
export const NavBarLinkItemHTMLAttributesSchema = (): z.ZodDefault => linkHTMLAttributesSchema.default({})
-const NavBarLinkItemSchema = NavBarBaseSchema.extend({
+const NavBarLinkItemSchema = NavBarWithIconSchema.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(),
/** HTML attributes to add to the link item. */
@@ -32,7 +35,7 @@ const NavBarLinkItemSchema = NavBarBaseSchema.extend({
}).strict()
export type SidebarLinkItem = z.infer
-const InternalNavBarLinkItemSchema = NavBarBaseSchema.partial({ label: true }).extend({
+const InternalNavBarLinkItemSchema = NavBarWithIconSchema.extend({
/** The link to this item’s content. Must be a slug of a Content Collection entry. */
slug: z.string(),
/** HTML attributes to add to the link item. */
@@ -49,3 +52,11 @@ export const NavBarItemSchema = z.union([
InternalNavBarLinkItemShorthandSchema,
])
export type NavBarItem = z.infer
+
+export const SubNavBarItemSchema = NavBarBaseSchema.extend({
+ /** The link to this item’s content. Must be a slug of a Content Collection entry. */
+ slug: z.string(),
+ /** HTML attributes to add to the link item. */
+ attrs: NavBarLinkItemHTMLAttributesSchema(),
+})
+export type SubNavBarItem = z.infer
diff --git a/src/utils/navigation.ts b/src/utils/navigation.ts
index c8e142c..ba10cc6 100644
--- a/src/utils/navigation.ts
+++ b/src/utils/navigation.ts
@@ -3,6 +3,7 @@ import type {
LinkHTMLAttributes,
NavBarItem,
SidebarLinkItem,
+ SubNavBarItem,
} from '../schemas/navbar'
import type { VitesseConfig } from './user-config'
@@ -176,3 +177,84 @@ export function getNavbarFromConfig(
export function getNavBar(pathname: string, locale: string | undefined): NavBarEntry[] {
return getNavbarFromConfig(config.navBar, pathname, locale)
}
+
+export type SubNavBarEntry = Pick
+
+export type SubNavBarLink = SubNavBarEntry
+
+function makeSubNavLink({
+ isCurrent = false,
+ attrs = {},
+ ...opts
+}: {
+ label: string
+ href: string
+ isCurrent?: boolean
+ attrs?: LinkHTMLAttributes | undefined
+}): SubNavBarLink {
+ return { ...opts, isCurrent, attrs }
+}
+
+function makeSubNavBarLink({ currentPathname, href, label, attrs }: {
+ href: string
+ label: string
+ currentPathname: string
+ attrs?: LinkHTMLAttributes
+}): SubNavBarLink {
+ if (!isAbsolute(href)) {
+ href = formatPath(href)
+ }
+ const isCurrent = pathsMatch(encodeURI(href), currentPathname)
+ return makeSubNavLink({ label, href, isCurrent, attrs })
+}
+
+function linkFromInternalSubNavBarLinkItem(
+ item: SubNavBarItem,
+ locale: string | undefined,
+ currentPathname: string,
+): SubNavBarLink {
+ // Astro passes root `index.[md|mdx]` entries with a slug of `index`
+ const slug = item.slug === 'index' ? '' : item.slug
+ const localizedSlug = locale ? (slug ? `${locale}/${slug}` : locale) : slug
+ const entry = routes.find(entry => localizedSlug === entry.slug)
+ if (!entry) {
+ const hasExternalSlashes = item.slug.at(0) === '/' || item.slug.at(-1) === '/'
+ if (hasExternalSlashes) {
+ throw new AstroError(
+ `The slug \`"${item.slug}"\` specified in the Vitesse navBar config must not start or end with a slash.`,
+ `Please try updating \`"${item.slug}"\` to \`"${stripLeadingAndTrailingSlashes(item.slug)}"\`.`,
+ )
+ }
+ else {
+ throw new AstroError(
+ `The slug \`"${item.slug}"\` specified in the Vitesse navBar config does not exist.`,
+ 'Update the Vitesse config to reference a valid entry slug in the docs content collection.\n'
+ + 'Learn more about Astro content collection slugs at https://docs.astro.build/en/reference/api-reference/#getentry',
+ )
+ }
+ }
+ const label
+ = pickLang(item.translations, localeToLang(locale)) || item.label || entry.entry.data.title
+ return makeSubNavBarLink({
+ href: entry.slug,
+ label,
+ currentPathname,
+ attrs: item.attrs,
+ })
+}
+
+function configItemToEntrySubNavBar(
+ item: SubNavBarItem,
+ currentPathname: string,
+ locale: string | undefined,
+): SubNavBarEntry {
+ return linkFromInternalSubNavBarLinkItem(item, locale, currentPathname)
+}
+
+export function getSubNavbarFromConfig(subNavBarConfig: VitesseConfig['subNavBar'], pathname: string, locale: string | undefined): SubNavBarEntry[] {
+ if (!subNavBarConfig) {
+ return []
+ }
+
+ return subNavBarConfig.map(subNavBar => configItemToEntrySubNavBar(subNavBar, pathname, locale))
+}
diff --git a/src/utils/user-config.ts b/src/utils/user-config.ts
index f9df09a..a3a7fd6 100644
--- a/src/utils/user-config.ts
+++ b/src/utils/user-config.ts
@@ -5,7 +5,7 @@ import { ComponentConfigSchema } from '../schemas/components'
import { FaviconSchema } from '../schemas/favicon'
import { HeadConfigSchema } from '../schemas/head'
import { LogoConfigSchema } from '../schemas/logo'
-import { NavBarItemSchema } from '../schemas/navbar'
+import { NavBarItemSchema, SubNavBarItemSchema } from '../schemas/navbar'
import { TitleConfigSchema, TitleTransformConfigSchema } from '../schemas/site-title'
import { SocialLinksSchema } from '../schemas/social'
import { BuiltInDefaultLocale } from './i18n'
@@ -158,6 +158,7 @@ const UserConfigSchema = z.object({
/** Configure your site’s sidebar navigation items. */
navBar: NavBarItemSchema.array().optional(),
+ subNavBar: SubNavBarItemSchema.array().optional(),
})
export const VitesseConfigSchema = UserConfigSchema.strict()