Skip to content

Commit

Permalink
feat: add subNavBar config
Browse files Browse the repository at this point in the history
  • Loading branch information
adrian-ub committed Oct 7, 2024
1 parent 86c2fbe commit b5c1718
Show file tree
Hide file tree
Showing 5 changed files with 137 additions and 15 deletions.
22 changes: 22 additions & 0 deletions playground/astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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',
},
],
}),
],
})
30 changes: 18 additions & 12 deletions src/components/SubNav.astro
Original file line number Diff line number Diff line change
@@ -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)
---

<div class="prose m-auto mb-8 select-none animate-none! op100!">
<div mb-0 flex="~ col gap-1 sm:row sm:gap-3 wrap" text-3xl>
<a href="/posts" class:list={['!border-none', currentPath === '/posts' ? activeStyle : inactiveStyle]}> Blog </a>
<a href="/talks" class:list={['!border-none', currentPath === '/talks' ? activeStyle : inactiveStyle]}> Talks </a>
<a href="/podcasts" class:list={['!border-none', currentPath === '/podcasts' ? activeStyle : inactiveStyle]}>
Podcasts
</a>
<a href="/streams" class:list={['!border-none', currentPath === '/streams' ? activeStyle : inactiveStyle]}>
Streams
</a>
<a href="/notes" class:list={['!border-none', currentPath === '/notes' ? activeStyle : inactiveStyle]}> Notes </a>
{
subNavBar.map((subNavLink) => (
<a
href={subNavLink.href}
class:list={['!border-none', subNavLink.isCurrent ? 'opacity-100 underline' : 'opacity-20 hover:opacity-50']}
{...subNavLink.attrs}
>
{subNavLink.label}
</a>
))
}
</div>
</div>
15 changes: 13 additions & 2 deletions src/schemas/navbar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(''),
Expand All @@ -24,15 +27,15 @@ export type LinkHTMLAttributes = z.infer<typeof linkHTMLAttributesSchema>

export const NavBarLinkItemHTMLAttributesSchema = (): z.ZodDefault<typeof linkHTMLAttributesSchema> => 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. */
attrs: NavBarLinkItemHTMLAttributesSchema(),
}).strict()
export type SidebarLinkItem = z.infer<typeof NavBarLinkItemSchema>

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. */
Expand All @@ -49,3 +52,11 @@ export const NavBarItemSchema = z.union([
InternalNavBarLinkItemShorthandSchema,
])
export type NavBarItem = z.infer<typeof NavBarItemSchema>

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<typeof SubNavBarItemSchema>
82 changes: 82 additions & 0 deletions src/utils/navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type {
LinkHTMLAttributes,
NavBarItem,
SidebarLinkItem,
SubNavBarItem,
} from '../schemas/navbar'
import type { VitesseConfig } from './user-config'

Expand Down Expand Up @@ -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<Link, 'label' | 'href' | 'isCurrent' | 'attrs'>

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))
}
3 changes: 2 additions & 1 deletion src/utils/user-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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()
Expand Down

0 comments on commit b5c1718

Please sign in to comment.