diff --git a/src/app/docs/[...slug]/page.tsx b/src/app/docs/[...slug]/page.tsx index 53837a9..f5d7319 100644 --- a/src/app/docs/[...slug]/page.tsx +++ b/src/app/docs/[...slug]/page.tsx @@ -1,7 +1,14 @@ -import type { EntryType } from "@/collections" +import type { EntryType, frontmatterSchema } from "@/collections" import type { Metadata } from "next" +import type { z } from "zod" +import { cache } from "react" import { notFound } from "next/navigation" -import { CollectionInfo, getFileContent, getSections } from "@/collections" +import { + CollectionInfo, + getFileContent, + getSections, + getTitle, +} from "@/collections" import { SiteBreadcrumb } from "@/components/breadcrumb" import { Comments } from "@/components/comments" import SectionGrid from "@/components/section-grid" @@ -36,47 +43,42 @@ interface PageProps { params: Promise<{ slug: string[] }> } -async function getBreadcrumbItems(slug: string[]) { - // we do not want to have "docs" as breadcrumb element - // also, we do not need the index file in our breadcrumb - const combinations = removeFromArray(slug, ["docs", "index"]).reduce( - (acc: string[][], curr) => acc.concat(acc.map((sub) => [...sub, curr])), - [[]], +const getBreadcrumbItems = cache(async (slug: string[]) => { + // we do not want to have "index" as breadcrumb element + const cleanedSlug = removeFromArray(slug, ["index"]) + + const combinations = cleanedSlug.map((_, index) => + cleanedSlug.slice(0, index + 1), ) const items = [] for (const currentPageSegement of combinations) { - let collection + let collection: EntryType + let file: Awaited> + let frontmatter: z.infer | undefined try { collection = await CollectionInfo.getEntry(currentPageSegement) - // eslint-disable-next-line @typescript-eslint/no-unused-vars + if (collection.getPathSegments().includes("index")) { + file = await getFileContent(collection.getParent()) + } else { + file = await getFileContent(collection) + } + + frontmatter = await file?.getExportValue("frontmatter") } catch (e: unknown) { continue } - if (isDirectory(collection)) { + if (!frontmatter) { items.push({ title: collection.getTitle(), path: ["docs", ...collection.getPathSegments()], }) } else { - const file = await getFileContent(collection) - - if (!file) { - continue - } - const frontmatter = await file.getExportValue("frontmatter") - - // in case we have an index file inside a directory - // we have also to fetch the directory name, otherwise we get "Index" as title - // if there is no `frontmatter.navTitle` defined - const parentTitle = collection.getPathSegments().includes("index") - ? collection.getParent().getTitle() - : null - + const title = getTitle(collection, frontmatter, true) items.push({ - title: frontmatter.navTitle ?? parentTitle ?? collection.getTitle(), + title, path: [ "docs", ...removeFromArray(collection.getPathSegments(), ["index"]), @@ -86,17 +88,13 @@ async function getBreadcrumbItems(slug: string[]) { } return items -} - -async function getParentTitle(slug: string[]) { - const elements = await getBreadcrumbItems(slug) - - return elements.map((ele) => ele.title) -} +}) export async function generateMetadata(props: PageProps): Promise { const params = await props.params - const titles = await getParentTitle(params.slug) + const breadcrumbItems = await getBreadcrumbItems(params.slug) + + const titles = breadcrumbItems.map((ele) => ele.title) return { title: titles.join(" - "), @@ -122,13 +120,21 @@ export default async function DocsPage(props: PageProps) { // if we can't find an index file, but we have a valid directory // use the directory component for rendering if (!file && isDirectory(collection)) { - return + return ( + <> + + + ) } // if we have a valid file ( including the index file ) // use the file component for rendering if (file) { - return + return ( + <> + + + ) } // seems to be an invalid path @@ -143,8 +149,8 @@ async function DirectoryContent({ source }: { source: EntryType }) { return ( <>
-
-
+
+
@@ -156,6 +162,7 @@ async function DirectoryContent({ source }: { source: EntryType }) { "prose-code:before:hidden prose-code:after:hidden", // use full width "w-full max-w-full", + "prose-a:text-indigo-400 prose-a:hover:text-white", )} >

- {headings.length > 0 && } + {headings.length > 0 && frontmatter.showToc && ( + + )}
0, + className={cn("gap-8 xl:grid", { + "mt-12 xl:mt-0": frontmatter.showToc && headings.length > 0, + "xl:grid-cols-[1fr_300px]": + frontmatter.showToc && headings.length > 0, + "xl:grid-cols-1": !frontmatter.showToc || headings.length == 0, })} > -
+
0, + })} + >

{frontmatter.title ?? source.getTitle()}

-

+

{frontmatter.description ?? ""}

@@ -256,27 +273,29 @@ async function FileContent({ source }: { source: EntryType }) {
-
- - -
- - - {lastUpdate && ( -
- Last updated: {format(lastUpdate, "dd.MM.yyyy")} + {frontmatter.showToc && headings.length > 0 ? ( +
+ + +
+ - )} + + {lastUpdate && ( +
+ Last updated: {format(lastUpdate, "dd.MM.yyyy")} +
+ )} +
-
+ ) : null}
diff --git a/src/collections.ts b/src/collections.ts index 09f32cc..1309f1e 100644 --- a/src/collections.ts +++ b/src/collections.ts @@ -14,6 +14,7 @@ export const frontmatterSchema = z.object({ navTitle: z.string().optional(), entrypoint: z.string().optional(), alias: z.string().optional(), + showToc: z.boolean().optional().default(true), }) export const headingSchema = z.array( @@ -86,6 +87,16 @@ export type DirectoryType = Awaited< ReturnType > +export function getTitle( + collection: EntryType, + frontmatter: z.infer, + includeTitle = false, +) { + return includeTitle + ? (frontmatter.navTitle ?? frontmatter.title ?? collection.getTitle()) + : (frontmatter.navTitle ?? collection.getTitle()) +} + export async function getDirectoryContent(source: EntryType) { // first, try to get the file based on the given path diff --git a/src/components/data-table-builder.tsx b/src/components/data-table/data-table-builder.tsx similarity index 100% rename from src/components/data-table-builder.tsx rename to src/components/data-table/data-table-builder.tsx diff --git a/src/components/data-table-column-header.tsx b/src/components/data-table/data-table-column-header.tsx similarity index 100% rename from src/components/data-table-column-header.tsx rename to src/components/data-table/data-table-column-header.tsx diff --git a/src/components/data-table-pagination.tsx b/src/components/data-table/data-table-pagination.tsx similarity index 100% rename from src/components/data-table-pagination.tsx rename to src/components/data-table/data-table-pagination.tsx diff --git a/src/components/heading.tsx b/src/components/heading.tsx new file mode 100644 index 0000000..68ce80c --- /dev/null +++ b/src/components/heading.tsx @@ -0,0 +1,36 @@ +import { ElementType, ReactNode } from "react" + +type IntrinsicElement = keyof JSX.IntrinsicElements +type PolymorphicComponentProps = { + as?: T +} & JSX.IntrinsicElements[T] + +const PolymorphicComponent = ({ + as: elementType = "div" as T, + ...rest +}: PolymorphicComponentProps) => { + const Component = elementType as ElementType + return +} + +export function Heading({ + level, + id, + children, +}: { + level: number + id: string + children: ReactNode +}) { + return ( + + {children}{" "} + + # + + + ) +} diff --git a/src/components/sidebar.tsx b/src/components/sidebar.tsx index cc4b7e0..200e305 100644 --- a/src/components/sidebar.tsx +++ b/src/components/sidebar.tsx @@ -12,6 +12,7 @@ import { SidebarItem, SidebarLabel, } from "@/components/ui/sidebar" +import { useIsMobile } from "@/hooks/use-mobile" import { current } from "@/lib/helpers" import { cn } from "@/lib/utils" import { ChevronsUpDown } from "lucide-react" @@ -42,6 +43,7 @@ export function SiteSidebar({ defaultHidden?: boolean }) { const pathname = usePathname() + const isMobile = useIsMobile() return ( @@ -60,9 +62,9 @@ export function SiteSidebar({
diff --git a/src/components/table-of-contents.tsx b/src/components/table-of-contents.tsx index eeb9458..aff8eb4 100644 --- a/src/components/table-of-contents.tsx +++ b/src/components/table-of-contents.tsx @@ -68,7 +68,7 @@ export function MobileTableOfContents({ toc }: TocProps) { const filteredToc = toc.filter((item) => item.depth > 1 && item.depth <= 4) return ( -
+
diff --git a/src/components/ui/sidebar.tsx b/src/components/ui/sidebar.tsx index 6aa7318..7270e72 100644 --- a/src/components/ui/sidebar.tsx +++ b/src/components/ui/sidebar.tsx @@ -22,7 +22,7 @@ const SidebarLayout = React.forwardRef< ref={ref} data-sidebar={state} className={cn( - "bg-accent/50 top-20 flex min-h-screen pl-0 transition-all duration-300 ease-in-out data-[sidebar=closed]:pl-0 sm:pl-[calc(theme(width.64))]", + "bg-accent/50 top-20 flex min-h-screen pl-0 transition-all duration-300 ease-in-out data-[sidebar=closed]:pl-0 sm:pl-[calc(theme(width.72))]", className, )} {...props} @@ -84,7 +84,7 @@ const Sidebar = React.forwardRef<