Vectrum
Sign In
JjohannSoAdd wikisHistory
124 lines · 4.1 KB · TypeScript
import Link from "next/link";
import type { ReactNode } from "react";
import { CloneUrlTabs, type CloneUrls } from "@/components/clone-button";
import { BookIcon, FileIcon, PlusIcon } from "@/components/icons";
import { Markdown } from "@/components/markdown";
import { Box } from "@/components/ui";
import type { WikiPage } from "@/lib/wiki";
import { pageHref } from "@/lib/wiki-links";

/** How many pages the sidebar lists before linking to all of them. */
const SIDEBAR_PAGES = 20;

/** An engraved heading over a sidebar group, with an optional readout at its right. */
function SectionLabel({ children, count }: { children: ReactNode; count?: number }) {
  return (
    <h2 className="mb-2 flex items-center justify-between type-label engraved">
      {children}
      {count !== undefined && <span className="type-readout text-ink3 [text-shadow:none]">{count}</span>}
    </h2>
  );
}

const quietLinkClass = "inline-flex items-center gap-1.5 type-meta text-ink2 transition-colors hover:text-ink [&_svg]:size-3";

/** "+ Add a custom sidebar": where a writer starts one of the special pages. */
export function AddSpecialLink({ wiki, slug, children }: { wiki: string; slug: string; children: ReactNode }) {
  return (
    <Link href={`${wiki}/_new?title=${encodeURIComponent(slug)}`} className={quietLinkClass}>
      <PlusIcon /> {children}
    </Link>
  );
}

/**
 * The wiki's sidebar, like GitHub's: its pages, the custom _Sidebar page if there is one, and the
 * clone URL.
 */
export function WikiSidebar({
  wiki,
  pages,
  current,
  sidebar,
  canWrite,
  cloneUrls,
}: {
  wiki: string;
  pages: WikiPage[];
  current?: string;
  /** The _Sidebar page's Markdown. */
  sidebar: string | null;
  canWrite: boolean;
  cloneUrls: CloneUrls;
}) {
  return (
    <aside className="flex flex-col gap-7">
      <section>
        <SectionLabel count={pages.length}>Pages</SectionLabel>
        {pages.length === 0 ? (
          <p className="type-body text-ink3">No pages yet.</p>
        ) : (
          <ul>
            {pages.slice(0, SIDEBAR_PAGES).map((p) => (
              <li key={p.path}>
                <Link
                  href={pageHref(wiki, p.slug)}
                  aria-current={p.slug === current ? "page" : undefined}
                  className={`group -mx-2 flex min-h-8 items-center gap-2.5 rounded-row px-2 py-1 type-ui outline-none hover:recipe-row-list-hover focus-visible:focus-ring ${
                    p.slug === current ? "font-medium text-ink recipe-row-list-hover" : "text-ink2 hover:text-ink"
                  }`}
                >
                  <FileIcon width={14} height={14} className="shrink-0 text-ink3 transition-colors group-hover:text-ink2" />
                  <span className="min-w-0 truncate">{p.title}</span>
                </Link>
              </li>
            ))}
          </ul>
        )}
        {pages.length > SIDEBAR_PAGES && (
          <Link href={`${wiki}/_pages`} className={`${quietLinkClass} mt-2`}>
            Show all {pages.length} pages
          </Link>
        )}
      </section>

      {sidebar !== null ? (
        <Box className="px-5 py-4 text-[13px]">
          <Markdown wiki={wiki}>{sidebar}</Markdown>
          {canWrite && (
            <Link href={`${wiki}/_Sidebar/_edit`} className={`${quietLinkClass} mt-3`}>
              Edit sidebar
            </Link>
          )}
        </Box>
      ) : (
        canWrite && (
          <div>
            <AddSpecialLink wiki={wiki} slug="_Sidebar">
              Add a custom sidebar
            </AddSpecialLink>
          </div>
        )
      )}

      <section>
        <SectionLabel>
          <span className="inline-flex items-center gap-1.5">
            <BookIcon width={12} height={12} /> Clone this wiki locally
          </span>
        </SectionLabel>
        <CloneUrlTabs urls={cloneUrls} />
      </section>
    </aside>
  );
}

/** The wiki's two columns: the page, then the sidebar. */
export function WikiColumns({ children, sidebar }: { children: ReactNode; sidebar: ReactNode }) {
  return (
    <div className="grid gap-8 lg:grid-cols-[minmax(0,1fr)_280px] xl:gap-10">
      <div className="min-w-0">{children}</div>
      {sidebar}
    </div>
  );
}