@brendonovich/kobalte__solidbase
Version:
Fully featured, fully customisable static site generation for SolidStart
129 lines (104 loc) • 3.17 kB
text/typescript
import { createContextProvider } from "@solid-primitives/context";
import { useLocation } from "@solidjs/router";
import { type Accessor, createMemo } from "solid-js";
import type {
SidebarConfig,
SidebarItem,
SidebarItemLink,
} from "../config/sidebar.js";
import { useLocale } from "./locale.js";
export type * from "../config/sidebar.js";
const [SidebarProvider, useSidebarRaw] = createContextProvider(
(props: { config?: SidebarConfig }) => {
const locale = useLocale();
const sidebars = createMemo(() => {
const sidebarConfig = props.config;
if (!sidebarConfig) return;
if (Array.isArray(sidebarConfig)) {
return { "/": sidebarConfig };
}
if ("items" in sidebarConfig) {
return { "/": sidebarConfig.items as SidebarItem[] };
}
for (const key in sidebarConfig) {
if ("items" in sidebarConfig[key as keyof SidebarConfig]) {
sidebarConfig[key as keyof SidebarConfig] =
// @ts-expect-error backwards compat
sidebarConfig[key as keyof SidebarConfig].items as SidebarItem[];
}
}
return sidebarConfig;
});
const sidebar = createMemo(() => {
const s = sidebars();
if (!s) return;
const sidebarsEntries = Object.entries(s);
if (sidebarsEntries.length === 1) {
const [prefix, sidebar] = sidebarsEntries[0];
return { prefix, items: sidebar };
}
sidebarsEntries.sort(([a], [b]) => b.length - a.length);
for (const [prefix, sidebar] of sidebarsEntries) {
if (locale.routePath().startsWith(prefix))
return { prefix, items: sidebar };
}
});
return sidebar;
},
);
export { SidebarProvider };
export function useSidebar<T = {}>() {
const s = useSidebarRaw();
if (!s)
throw new Error("useSidebar must be called underneath a SidebarProvider");
return s as Accessor<{ prefix: string; items: SidebarItem<T>[] }>;
}
function flattenSidebarItems<T = {}>(
sidebar: { prefix: string; items: SidebarItem<T>[] },
depth = 0,
): Array<SidebarItemLink & T & { depth: number }> {
return sidebar.items.flatMap((item) => {
if ("link" in item)
return {
target: (() => {
if (item.link.includes("//")) return "_blank";
})(),
rel: (() => {
if (item.link.includes("//") || item.target === "_blank")
return "noopener noreferrer";
})(),
...item,
link: (() => {
if (sidebar.prefix === "/") return item.link;
if (item.link.endsWith("/"))
return `${sidebar.prefix}${item.link.slice(0, -1)}`;
return `${sidebar.prefix}${item.link}`;
})(),
depth,
};
return flattenSidebarItems<T>(
{ prefix: sidebar.prefix + (item.base ?? ""), items: item.items },
depth + 1,
);
});
}
export function usePrevNext<T = {}>() {
const sidebar = useSidebar<T>();
const links = createMemo(() => {
const s = sidebar();
if (!s) return [];
return flattenSidebarItems<T>(s);
});
const location = useLocation();
const index = createMemo(() => {
const s = sidebar();
if (!s) return -1;
return links().findIndex(
(item) => "link" in item && location.pathname === item.link,
);
});
return {
prevLink: () => links()[index() - 1],
nextLink: () => links()[index() + 1],
};
}